mirror of
https://github.com/gabehf/Koito.git
synced 2026-03-07 13:38:15 -08:00
feat: v0.0.5
This commit is contained in:
parent
4c4ebc593d
commit
242a82ad8c
36 changed files with 694 additions and 174 deletions
|
|
@ -74,13 +74,13 @@ function mergeTracks(from: number, to: number): Promise<Response> {
|
|||
method: "POST",
|
||||
})
|
||||
}
|
||||
function mergeAlbums(from: number, to: number): Promise<Response> {
|
||||
return fetch(`/apis/web/v1/merge/albums?from_id=${from}&to_id=${to}`, {
|
||||
function mergeAlbums(from: number, to: number, replaceImage: boolean): Promise<Response> {
|
||||
return fetch(`/apis/web/v1/merge/albums?from_id=${from}&to_id=${to}&replace_image=${replaceImage}`, {
|
||||
method: "POST",
|
||||
})
|
||||
}
|
||||
function mergeArtists(from: number, to: number): Promise<Response> {
|
||||
return fetch(`/apis/web/v1/merge/artists?from_id=${from}&to_id=${to}`, {
|
||||
function mergeArtists(from: number, to: number, replaceImage: boolean): Promise<Response> {
|
||||
return fetch(`/apis/web/v1/merge/artists?from_id=${from}&to_id=${to}&replace_image=${replaceImage}`, {
|
||||
method: "POST",
|
||||
})
|
||||
}
|
||||
|
|
@ -200,6 +200,7 @@ type Track = {
|
|||
image: string
|
||||
album_id: number
|
||||
musicbrainz_id: string
|
||||
time_listened: number
|
||||
}
|
||||
type Artist = {
|
||||
id: number
|
||||
|
|
@ -208,6 +209,7 @@ type Artist = {
|
|||
aliases: string[]
|
||||
listen_count: number
|
||||
musicbrainz_id: string
|
||||
time_listened: number
|
||||
}
|
||||
type Album = {
|
||||
id: number,
|
||||
|
|
@ -217,6 +219,7 @@ type Album = {
|
|||
is_various_artists: boolean
|
||||
artists: SimpleArtists[]
|
||||
musicbrainz_id: string
|
||||
time_listened: number
|
||||
}
|
||||
type Alias = {
|
||||
id: number
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function ItemCard({ item, type }: { item: Item; type: "album" | "track" | "artis
|
|||
<span className="color-fg-secondary">Various Artists</span>
|
||||
:
|
||||
<div onClick={handleArtistClick} onKeyDown={handleArtistKeyDown}>
|
||||
<ArtistLinks artists={album.artists || [{id: 0, Name: 'Unknown Artist'}]}/>
|
||||
<ArtistLinks artists={album.artists ? [album.artists[0]] : [{id: 0, name: 'Unknown Artist'}]}/>
|
||||
</div>
|
||||
}
|
||||
<div className="color-fg-secondary">{album.listen_count} plays</div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default function MergeModal(props: Props) {
|
|||
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()
|
||||
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ export default function MergeModal(props: Props) {
|
|||
from = {id: props.currentId, title: props.currentTitle}
|
||||
to = mergeTarget
|
||||
}
|
||||
props.mergeFunc(from.id, to.id)
|
||||
props.mergeFunc(from.id, to.id, replaceImage)
|
||||
.then(r => {
|
||||
if (r.ok) {
|
||||
if (mergeOrderReversed) {
|
||||
|
|
@ -117,6 +118,13 @@ export default function MergeModal(props: Props) {
|
|||
<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)} />
|
||||
<label htmlFor="replace-image">Replace image</label>
|
||||
</div>
|
||||
}
|
||||
</> :
|
||||
''}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import LastPlays from "~/components/LastPlays";
|
|||
import PeriodSelector from "~/components/PeriodSelector";
|
||||
import MediaLayout from "./MediaLayout";
|
||||
import ActivityGrid from "~/components/ActivityGrid";
|
||||
import { timeListenedString } from "~/utils/utils";
|
||||
|
||||
export async function clientLoader({ params }: LoaderFunctionArgs) {
|
||||
const res = await fetch(`/apis/web/v1/album?id=${params.id}`);
|
||||
|
|
@ -40,9 +41,10 @@ export default function Album() {
|
|||
}
|
||||
return r
|
||||
}}
|
||||
subContent={<>
|
||||
subContent={<div className="flex flex-col gap-2 items-start">
|
||||
{album.listen_count && <p>{album.listen_count} play{ album.listen_count > 1 ? 's' : ''}</p>}
|
||||
</>}
|
||||
{<p>{timeListenedString(album.time_listened)}</p>}
|
||||
</div>}
|
||||
>
|
||||
<div className="mt-10">
|
||||
<PeriodSelector setter={setPeriod} current={period} />
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import PeriodSelector from "~/components/PeriodSelector";
|
|||
import MediaLayout from "./MediaLayout";
|
||||
import ArtistAlbums from "~/components/ArtistAlbums";
|
||||
import ActivityGrid from "~/components/ActivityGrid";
|
||||
import { timeListenedString } from "~/utils/utils";
|
||||
|
||||
export async function clientLoader({ params }: LoaderFunctionArgs) {
|
||||
const res = await fetch(`/apis/web/v1/artist?id=${params.id}`);
|
||||
|
|
@ -46,9 +47,10 @@ export default function Artist() {
|
|||
}
|
||||
return r
|
||||
}}
|
||||
subContent={<>
|
||||
subContent={<div className="flex flex-col gap-2 items-start">
|
||||
{artist.listen_count && <p>{artist.listen_count} play{ artist.listen_count > 1 ? 's' : ''}</p>}
|
||||
</>}
|
||||
{<p>{timeListenedString(artist.time_listened)}</p>}
|
||||
</div>}
|
||||
>
|
||||
<div className="mt-10">
|
||||
<PeriodSelector setter={setPeriod} current={period} />
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import ImageReplaceModal from "~/components/modals/ImageReplaceModal";
|
|||
import DeleteModal from "~/components/modals/DeleteModal";
|
||||
import RenameModal from "~/components/modals/RenameModal";
|
||||
|
||||
export type MergeFunc = (from: number, to: number) => Promise<Response>
|
||||
export type MergeFunc = (from: number, to: number, replaceImage: boolean) => Promise<Response>
|
||||
export type MergeSearchCleanerFunc = (r: SearchResponse, id: number) => SearchResponse
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import LastPlays from "~/components/LastPlays";
|
|||
import PeriodSelector from "~/components/PeriodSelector";
|
||||
import MediaLayout from "./MediaLayout";
|
||||
import ActivityGrid from "~/components/ActivityGrid";
|
||||
import { timeListenedString } from "~/utils/utils";
|
||||
|
||||
export async function clientLoader({ params }: LoaderFunctionArgs) {
|
||||
let res = await fetch(`/apis/web/v1/track?id=${params.id}`);
|
||||
|
|
@ -42,9 +43,10 @@ export default function Track() {
|
|||
}
|
||||
return r
|
||||
}}
|
||||
subContent={<div className="flex flex-col gap-4 items-start">
|
||||
subContent={<div className="flex flex-col gap-2 items-start">
|
||||
<Link to={`/album/${track.album_id}`}>appears on {album.title}</Link>
|
||||
{track.listen_count && <p>{track.listen_count} play{ track.listen_count > 1 ? 's' : ''}</p>}
|
||||
{<p>{timeListenedString(track.time_listened)}</p>}
|
||||
</div>}
|
||||
>
|
||||
<div className="mt-10">
|
||||
|
|
|
|||
|
|
@ -86,5 +86,17 @@ const hexToHSL = (hex: string): hsl => {
|
|||
};
|
||||
};
|
||||
|
||||
export {hexToHSL}
|
||||
const timeListenedString = (seconds: number) => {
|
||||
if (!seconds) return ""
|
||||
|
||||
if (seconds > (120 * 60) - 1) {
|
||||
let hours = Math.floor(seconds / 60 / 60)
|
||||
return `${hours} hours listened`
|
||||
} else {
|
||||
let minutes = Math.floor(seconds / 60)
|
||||
return `${minutes} minutes listened`
|
||||
}
|
||||
}
|
||||
|
||||
export {hexToHSL, timeListenedString}
|
||||
export type {hsl}
|
||||
Loading…
Add table
Add a link
Reference in a new issue