mirror of
https://github.com/gabehf/Koito.git
synced 2026-04-22 12:01:52 -07:00
wip: initial ui done
This commit is contained in:
parent
b5e8d88451
commit
442221327d
5 changed files with 133 additions and 120 deletions
|
|
@ -63,7 +63,7 @@
|
||||||
@media (min-width: 60rem) {
|
@media (min-width: 60rem) {
|
||||||
:root {
|
:root {
|
||||||
--header-xl: 78px;
|
--header-xl: 78px;
|
||||||
--header-lg: 44px;
|
--header-lg: 36px;
|
||||||
--header-md: 22px;
|
--header-md: 22px;
|
||||||
--header-sm: 16px;
|
--header-sm: 16px;
|
||||||
--header-xl-weight: 600;
|
--header-xl-weight: 600;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { imageUrl, type RewindStats } from "api/api";
|
import { imageUrl, type RewindStats } from "api/api";
|
||||||
import RewindTopItem from "./RewindTopItem";
|
import RewindStatText from "./RewindStatText";
|
||||||
|
import { RewindTopItem } from "./RewindTopItem";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
stats: RewindStats;
|
stats: RewindStats;
|
||||||
includeTime: boolean;
|
includeTime?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Rewind(props: Props) {
|
export default function Rewind(props: Props) {
|
||||||
|
|
@ -11,103 +12,60 @@ export default function Rewind(props: Props) {
|
||||||
const albumimg = props.stats.top_albums[0].image;
|
const albumimg = props.stats.top_albums[0].image;
|
||||||
const trackimg = props.stats.top_tracks[0].image;
|
const trackimg = props.stats.top_tracks[0].image;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-10">
|
<div className="flex flex-col gap-7">
|
||||||
<h1>{props.stats.title}</h1>
|
<h1>{props.stats.title}</h1>
|
||||||
<div className="flex gap-5">
|
<RewindTopItem
|
||||||
<div className="rewind-top-item-image">
|
title="Top Artist"
|
||||||
<img className="w-58 h-58" src={imageUrl(artistimg, "medium")} />
|
imageSrc={imageUrl(artistimg, "medium")}
|
||||||
</div>
|
items={props.stats.top_artists}
|
||||||
<div className="flex flex-col gap-1">
|
getLabel={(a) => a.name}
|
||||||
<h4>Top Artist</h4>
|
includeTime={props.includeTime}
|
||||||
<div className="flex items-center gap-2">
|
/>
|
||||||
<div className="flex flex-col items-start mb-3">
|
|
||||||
<h2>{props.stats.top_artists[0].name}</h2>
|
<RewindTopItem
|
||||||
<span className="text-(--color-fg-tertiary) -mt-3">
|
title="Top Album"
|
||||||
{`${props.stats.top_artists[0].listen_count} plays`}
|
imageSrc={imageUrl(albumimg, "medium")}
|
||||||
{props.includeTime
|
items={props.stats.top_albums}
|
||||||
? ` (${Math.floor(
|
getLabel={(a) => a.title}
|
||||||
props.stats.top_artists[0].time_listened / 60
|
includeTime={props.includeTime}
|
||||||
)} minutes)`
|
/>
|
||||||
: ``}
|
|
||||||
</span>
|
<RewindTopItem
|
||||||
</div>
|
title="Top Track"
|
||||||
</div>
|
imageSrc={imageUrl(trackimg, "medium")}
|
||||||
{props.stats.top_artists.slice(1).map((e, i) => (
|
items={props.stats.top_tracks}
|
||||||
<div className="" key={e.id}>
|
getLabel={(t) => t.title}
|
||||||
{e.name}
|
includeTime={props.includeTime}
|
||||||
<span className="text-(--color-fg-tertiary)">
|
/>
|
||||||
{` - ${e.listen_count} plays`}
|
|
||||||
{props.includeTime
|
<div className="grid grid-cols-3 gap-5">
|
||||||
? ` (${Math.floor(e.time_listened / 60)} minutes)`
|
<RewindStatText
|
||||||
: ``}
|
figure={`${props.stats.minutes_listened}`}
|
||||||
</span>
|
text="Minutes listened"
|
||||||
</div>
|
/>
|
||||||
))}
|
<RewindStatText figure={`${props.stats.unique_tracks}`} text="Tracks" />
|
||||||
</div>
|
<RewindStatText
|
||||||
</div>
|
figure={`${props.stats.new_tracks}`}
|
||||||
<div className="flex gap-5">
|
text="New tracks"
|
||||||
<div className="rewind-top-item-image">
|
/>
|
||||||
<img className="w-58 h-58" src={imageUrl(albumimg, "medium")} />
|
<RewindStatText figure={`${props.stats.plays}`} text="Plays" />
|
||||||
</div>
|
<RewindStatText figure={`${props.stats.unique_albums}`} text="Albums" />
|
||||||
<div className="flex flex-col gap-1">
|
<RewindStatText
|
||||||
<h4>Top Album</h4>
|
figure={`${props.stats.new_albums}`}
|
||||||
<div className="flex items-center gap-2">
|
text="New albums"
|
||||||
<div className="flex flex-col items-start mb-3">
|
/>
|
||||||
<h2>{props.stats.top_albums[0].title}</h2>
|
<RewindStatText
|
||||||
<span className="text-(--color-fg-tertiary) -mt-3">
|
figure={`${props.stats.avg_plays_per_day.toFixed(1)}`}
|
||||||
{`${props.stats.top_albums[0].listen_count} plays`}
|
text="Plays per day"
|
||||||
{props.includeTime
|
/>
|
||||||
? ` (${Math.floor(
|
<RewindStatText
|
||||||
props.stats.top_albums[0].time_listened / 60
|
figure={`${props.stats.unique_artists}`}
|
||||||
)} minutes)`
|
text="Artists"
|
||||||
: ``}
|
/>
|
||||||
</span>
|
<RewindStatText
|
||||||
</div>
|
figure={`${props.stats.new_artists}`}
|
||||||
</div>
|
text="New artists"
|
||||||
{props.stats.top_albums.slice(1).map((e, i) => (
|
/>
|
||||||
<div className="" key={e.id}>
|
|
||||||
{e.title}
|
|
||||||
<span className="text-(--color-fg-tertiary)">
|
|
||||||
{` - ${e.listen_count} plays`}
|
|
||||||
{props.includeTime
|
|
||||||
? ` (${Math.floor(e.time_listened / 60)} minutes)`
|
|
||||||
: ``}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-5">
|
|
||||||
<div className="rewind-top-item-image">
|
|
||||||
<img className="w-58 h-58" src={imageUrl(trackimg, "medium")} />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<h4>Top Track</h4>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex flex-col items-start mb-3">
|
|
||||||
<h2>{props.stats.top_tracks[0].title}</h2>
|
|
||||||
<span className="text-(--color-fg-tertiary) -mt-3">
|
|
||||||
{`${props.stats.top_tracks[0].listen_count} plays`}
|
|
||||||
{props.includeTime
|
|
||||||
? ` (${Math.floor(
|
|
||||||
props.stats.top_tracks[0].time_listened / 60
|
|
||||||
)} minutes)`
|
|
||||||
: ``}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{props.stats.top_tracks.slice(1).map((e, i) => (
|
|
||||||
<div className="" key={e.id}>
|
|
||||||
{e.title}
|
|
||||||
<span className="text-(--color-fg-tertiary)">
|
|
||||||
{` - ${e.listen_count} plays`}
|
|
||||||
{props.includeTime
|
|
||||||
? ` (${Math.floor(e.time_listened / 60)} minutes)`
|
|
||||||
: ``}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
32
client/app/components/rewind/RewindStatText.tsx
Normal file
32
client/app/components/rewind/RewindStatText.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
interface Props {
|
||||||
|
figure: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RewindStatText(props: Props) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-baseline gap-1.5">
|
||||||
|
<div className="w-20 text-end shrink-0">
|
||||||
|
<span
|
||||||
|
className="
|
||||||
|
relative inline-block
|
||||||
|
text-2xl font-semibold
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="
|
||||||
|
absolute inset-0
|
||||||
|
-translate-x-2 translate-y-8
|
||||||
|
bg-(--color-primary)
|
||||||
|
z-0
|
||||||
|
h-1
|
||||||
|
"
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<span className="relative z-1">{props.figure}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">{props.text}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,30 +1,53 @@
|
||||||
import { imageUrl, type Artist } from "api/api";
|
type TopItemProps<T> = {
|
||||||
|
title: string;
|
||||||
|
imageSrc: string;
|
||||||
|
items: T[];
|
||||||
|
getLabel: (item: T) => string;
|
||||||
|
includeTime?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
interface args {
|
export function RewindTopItem<
|
||||||
title?: string;
|
T extends {
|
||||||
name?: string;
|
id: string | number;
|
||||||
image: string;
|
listen_count: number;
|
||||||
minutes_listened: number;
|
time_listened: number;
|
||||||
time_listened: number;
|
|
||||||
artists?: Artist;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function RewindTopItem(args: args[]) {
|
|
||||||
console.log(args);
|
|
||||||
if (args === undefined || args.length < 1) {
|
|
||||||
return <></>;
|
|
||||||
}
|
}
|
||||||
const img = imageUrl(args[0].image, "medium");
|
>({ title, imageSrc, items, getLabel, includeTime }: TopItemProps<T>) {
|
||||||
|
const [top, ...rest] = items;
|
||||||
|
|
||||||
|
if (!top) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-5">
|
||||||
<div className="rewind-top-item-image">
|
<div className="rewind-top-item-image">
|
||||||
<img src={img} />
|
<img className="w-50 h-50" src={imageSrc} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3>{args[0].title || args[0].name}</h3>
|
<h4 className="-mb-1">{title}</h4>
|
||||||
{args.map((e) => (
|
|
||||||
<div className="">{e.title || e.name}</div>
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="flex flex-col items-start mb-2">
|
||||||
|
<h2>{getLabel(top)}</h2>
|
||||||
|
<span className="text-(--color-fg-tertiary) -mt-3 text-sm">
|
||||||
|
{`${top.listen_count} plays`}
|
||||||
|
{includeTime
|
||||||
|
? ` (${Math.floor(top.time_listened / 60)} minutes)`
|
||||||
|
: ``}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{rest.map((e) => (
|
||||||
|
<div key={e.id} className="text-sm">
|
||||||
|
{getLabel(e)}
|
||||||
|
<span className="text-(--color-fg-tertiary)">
|
||||||
|
{` - ${e.listen_count} plays`}
|
||||||
|
{includeTime
|
||||||
|
? ` (${Math.floor(e.time_listened / 60)} minutes)`
|
||||||
|
: ``}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ export default function RewindPage() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRewindStats({ year: 2025 }).then((r) => setStats(r));
|
getRewindStats({ year: 2025 }).then((r) => setStats(r));
|
||||||
}, []);
|
}, []);
|
||||||
return <>{stats !== undefined && <Rewind stats={stats} includeTime />}</>;
|
return <>{stats !== undefined && <Rewind stats={stats} />}</>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue