mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 21:48:18 -08:00
feat: Rewind (#116)
* wip * chore: update counts to allow unix timeframe * feat: add db functions for counting new items * wip: endpoint working * wip * wip: initial ui done * add header, adjust ui * add time listened toggle * fix layout, year param * param fixes
This commit is contained in:
parent
c0a8c64243
commit
d4ac96f780
64 changed files with 2252 additions and 1055 deletions
|
|
@ -2,128 +2,158 @@ import { useEffect, useState } from "react";
|
|||
import { Modal } from "./Modal";
|
||||
import { search, type SearchResponse } from "api/api";
|
||||
import SearchResults from "../SearchResults";
|
||||
import type { MergeFunc, MergeSearchCleanerFunc } from "~/routes/MediaItems/MediaLayout";
|
||||
import type {
|
||||
MergeFunc,
|
||||
MergeSearchCleanerFunc,
|
||||
} from "~/routes/MediaItems/MediaLayout";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
setOpen: Function
|
||||
type: string
|
||||
currentId: number
|
||||
currentTitle: string
|
||||
mergeFunc: MergeFunc
|
||||
mergeCleanerFunc: MergeSearchCleanerFunc
|
||||
open: boolean;
|
||||
setOpen: Function;
|
||||
type: string;
|
||||
currentId: number;
|
||||
currentTitle: string;
|
||||
mergeFunc: MergeFunc;
|
||||
mergeCleanerFunc: MergeSearchCleanerFunc;
|
||||
}
|
||||
|
||||
export default function MergeModal(props: Props) {
|
||||
const [query, setQuery] = useState('');
|
||||
const [data, setData] = useState<SearchResponse>();
|
||||
const [debouncedQuery, setDebouncedQuery] = useState(query);
|
||||
const [mergeTarget, setMergeTarget] = useState<{title: string, id: number}>({title: '', id: 0})
|
||||
const [mergeOrderReversed, setMergeOrderReversed] = useState(false)
|
||||
const [replaceImage, setReplaceImage] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const [query, setQuery] = useState("");
|
||||
const [data, setData] = useState<SearchResponse>();
|
||||
const [debouncedQuery, setDebouncedQuery] = useState(query);
|
||||
const [mergeTarget, setMergeTarget] = useState<{ title: string; id: number }>(
|
||||
{ title: "", id: 0 }
|
||||
);
|
||||
const [mergeOrderReversed, setMergeOrderReversed] = useState(false);
|
||||
const [replaceImage, setReplaceImage] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const closeMergeModal = () => {
|
||||
props.setOpen(false);
|
||||
setQuery("");
|
||||
setData(undefined);
|
||||
setMergeOrderReversed(false);
|
||||
setMergeTarget({ title: "", id: 0 });
|
||||
};
|
||||
|
||||
const closeMergeModal = () => {
|
||||
props.setOpen(false)
|
||||
setQuery('')
|
||||
setData(undefined)
|
||||
setMergeOrderReversed(false)
|
||||
setMergeTarget({title: '', id: 0})
|
||||
const toggleSelect = ({ title, id }: { title: string; id: number }) => {
|
||||
setMergeTarget({ title: title, id: id });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("mergeTarget", mergeTarget);
|
||||
}, [mergeTarget]);
|
||||
|
||||
const doMerge = () => {
|
||||
let from, to;
|
||||
if (!mergeOrderReversed) {
|
||||
from = mergeTarget;
|
||||
to = { id: props.currentId, title: props.currentTitle };
|
||||
} else {
|
||||
from = { id: props.currentId, title: props.currentTitle };
|
||||
to = mergeTarget;
|
||||
}
|
||||
|
||||
const toggleSelect = ({title, id}: {title: string, id: number}) => {
|
||||
setMergeTarget({title: title, id: id})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("mergeTarget",mergeTarget)
|
||||
}, [mergeTarget])
|
||||
|
||||
const doMerge = () => {
|
||||
let from, to
|
||||
if (!mergeOrderReversed) {
|
||||
from = mergeTarget
|
||||
to = {id: props.currentId, title: props.currentTitle}
|
||||
props
|
||||
.mergeFunc(from.id, to.id, replaceImage)
|
||||
.then((r) => {
|
||||
if (r.ok) {
|
||||
if (mergeOrderReversed) {
|
||||
navigate(`/${props.type.toLowerCase()}/${mergeTarget.id}`);
|
||||
closeMergeModal();
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
from = {id: props.currentId, title: props.currentTitle}
|
||||
to = mergeTarget
|
||||
// TODO: handle error
|
||||
console.log(r);
|
||||
}
|
||||
props.mergeFunc(from.id, to.id, replaceImage)
|
||||
.then(r => {
|
||||
if (r.ok) {
|
||||
if (mergeOrderReversed) {
|
||||
navigate(`/${props.type.toLowerCase()}/${mergeTarget.id}`)
|
||||
closeMergeModal()
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
} else {
|
||||
// TODO: handle error
|
||||
console.log(r)
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log(err))
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedQuery(query);
|
||||
if (query === "") {
|
||||
setData(undefined);
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedQuery) {
|
||||
search(debouncedQuery).then((r) => {
|
||||
r = props.mergeCleanerFunc(r, props.currentId);
|
||||
setData(r);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedQuery(query);
|
||||
if (query === '') {
|
||||
setData(undefined)
|
||||
}
|
||||
}, 300);
|
||||
}, [debouncedQuery]);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [query]);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedQuery) {
|
||||
search(debouncedQuery).then((r) => {
|
||||
r = props.mergeCleanerFunc(r, props.currentId)
|
||||
setData(r);
|
||||
});
|
||||
}
|
||||
}, [debouncedQuery]);
|
||||
|
||||
return (
|
||||
return (
|
||||
<Modal isOpen={props.open} onClose={closeMergeModal}>
|
||||
<h2>Merge {props.type}s</h2>
|
||||
<div className="flex flex-col items-center">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
// i find my stupid a(n) logic to be a little silly so im leaving it in even if its not optimal
|
||||
placeholder={`Search for a${props.type.toLowerCase()[0] === 'a' ? 'n' : ''} ${props.type.toLowerCase()} to be merged into the current ${props.type.toLowerCase()}`}
|
||||
className="w-full mx-auto fg bg rounded p-2"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<SearchResults selectorMode data={data} onSelect={toggleSelect}/>
|
||||
{ mergeTarget.id !== 0 ?
|
||||
<>
|
||||
{mergeOrderReversed ?
|
||||
<p className="mt-5"><strong>{props.currentTitle}</strong> will be merged into <strong>{mergeTarget.title}</strong></p>
|
||||
:
|
||||
<p className="mt-5"><strong>{mergeTarget.title}</strong> will be merged into <strong>{props.currentTitle}</strong></p>
|
||||
}
|
||||
<button className="hover:cursor-pointer px-5 py-2 rounded-md mt-5 bg-(--color-bg) hover:bg-(--color-bg-tertiary)" onClick={doMerge}>Merge Items</button>
|
||||
<h3>Merge {props.type}s</h3>
|
||||
<div className="flex flex-col items-center">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
// i find my stupid a(n) logic to be a little silly so im leaving it in even if its not optimal
|
||||
placeholder={`Search for a${
|
||||
props.type.toLowerCase()[0] === "a" ? "n" : ""
|
||||
} ${props.type.toLowerCase()} to be merged into the current ${props.type.toLowerCase()}`}
|
||||
className="w-full mx-auto fg bg rounded p-2"
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<SearchResults selectorMode data={data} onSelect={toggleSelect} />
|
||||
{mergeTarget.id !== 0 ? (
|
||||
<>
|
||||
{mergeOrderReversed ? (
|
||||
<p className="mt-5">
|
||||
<strong>{props.currentTitle}</strong> will be merged into{" "}
|
||||
<strong>{mergeTarget.title}</strong>
|
||||
</p>
|
||||
) : (
|
||||
<p className="mt-5">
|
||||
<strong>{mergeTarget.title}</strong> will be merged into{" "}
|
||||
<strong>{props.currentTitle}</strong>
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
className="hover:cursor-pointer px-5 py-2 rounded-md mt-5 bg-(--color-bg) hover:bg-(--color-bg-tertiary)"
|
||||
onClick={doMerge}
|
||||
>
|
||||
Merge Items
|
||||
</button>
|
||||
<div className="flex gap-2 mt-3">
|
||||
<input type="checkbox" name="reverse-merge-order" checked={mergeOrderReversed} onChange={() => setMergeOrderReversed(!mergeOrderReversed)} />
|
||||
<label htmlFor="reverse-merge-order">Reverse merge order</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="reverse-merge-order"
|
||||
checked={mergeOrderReversed}
|
||||
onChange={() => setMergeOrderReversed(!mergeOrderReversed)}
|
||||
/>
|
||||
<label htmlFor="reverse-merge-order">Reverse merge order</label>
|
||||
</div>
|
||||
{
|
||||
(props.type.toLowerCase() === "album" || props.type.toLowerCase() === "artist") &&
|
||||
<div className="flex gap-2 mt-3">
|
||||
<input type="checkbox" name="replace-image" checked={replaceImage} onChange={() => setReplaceImage(!replaceImage)} />
|
||||
{(props.type.toLowerCase() === "album" ||
|
||||
props.type.toLowerCase() === "artist") && (
|
||||
<div className="flex gap-2 mt-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="replace-image"
|
||||
checked={replaceImage}
|
||||
onChange={() => setReplaceImage(!replaceImage)}
|
||||
/>
|
||||
<label htmlFor="replace-image">Replace image</label>
|
||||
</div>
|
||||
}
|
||||
</> :
|
||||
''}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue