fix: use minutes instead of hours for time listened

main
Gabe Farrell 2 weeks ago
parent 63d953b192
commit 383be25bfc

@ -19,42 +19,58 @@ export async function clientLoader({ params }: LoaderFunctionArgs) {
export default function Album() { export default function Album() {
const album = useLoaderData() as Album; const album = useLoaderData() as Album;
const [period, setPeriod] = useState('week') const [period, setPeriod] = useState("week");
console.log(album) console.log(album);
return ( return (
<MediaLayout type="Album" <MediaLayout
title={album.title} type="Album"
img={album.image} title={album.title}
id={album.id} img={album.image}
musicbrainzId={album.musicbrainz_id} id={album.id}
imgItemId={album.id} musicbrainzId={album.musicbrainz_id}
mergeFunc={mergeAlbums} imgItemId={album.id}
mergeCleanerFunc={(r, id) => { mergeFunc={mergeAlbums}
r.artists = [] mergeCleanerFunc={(r, id) => {
r.tracks = [] r.artists = [];
for (let i = 0; i < r.albums.length; i ++) { r.tracks = [];
if (r.albums[i].id === id) { for (let i = 0; i < r.albums.length; i++) {
delete r.albums[i] if (r.albums[i].id === id) {
} delete r.albums[i];
} }
return r }
}} return r;
subContent={<div className="flex flex-col gap-2 items-start"> }}
{album.listen_count && <p>{album.listen_count} play{ album.listen_count > 1 ? 's' : ''}</p>} subContent={
{<p title={Math.floor(album.time_listened / 60) + " minutes"}>{timeListenedString(album.time_listened)}</p>} <div className="flex flex-col gap-2 items-start">
{<p title={new Date(album.first_listen * 1000).toLocaleString()}>Listening since {new Date(album.first_listen * 1000).toLocaleDateString()}</p>} {album.listen_count && (
</div>} <p>
> {album.listen_count} play{album.listen_count > 1 ? "s" : ""}
<div className="mt-10"> </p>
<PeriodSelector setter={setPeriod} current={period} /> )}
</div> {
<div className="flex flex-wrap gap-20 mt-10"> <p title={Math.floor(album.time_listened / 60 / 60) + " hours"}>
<LastPlays limit={30} albumId={album.id} /> {timeListenedString(album.time_listened)}
<TopTracks limit={12} period={period} albumId={album.id} /> </p>
<ActivityGrid configurable albumId={album.id} /> }
{
<p title={new Date(album.first_listen * 1000).toLocaleString()}>
Listening since{" "}
{new Date(album.first_listen * 1000).toLocaleDateString()}
</p>
}
</div> </div>
}
>
<div className="mt-10">
<PeriodSelector setter={setPeriod} current={period} />
</div>
<div className="flex flex-wrap gap-20 mt-10">
<LastPlays limit={30} albumId={album.id} />
<TopTracks limit={12} period={period} albumId={album.id} />
<ActivityGrid configurable albumId={album.id} />
</div>
</MediaLayout> </MediaLayout>
); );
} }

@ -20,50 +20,66 @@ export async function clientLoader({ params }: LoaderFunctionArgs) {
export default function Artist() { export default function Artist() {
const artist = useLoaderData() as Artist; const artist = useLoaderData() as Artist;
const [period, setPeriod] = useState('week') const [period, setPeriod] = useState("week");
// remove canonical name from alias list // remove canonical name from alias list
console.log(artist.aliases) console.log(artist.aliases);
let index = artist.aliases.indexOf(artist.name); let index = artist.aliases.indexOf(artist.name);
if (index !== -1) { if (index !== -1) {
artist.aliases.splice(index, 1); artist.aliases.splice(index, 1);
} }
return ( return (
<MediaLayout type="Artist" <MediaLayout
title={artist.name} type="Artist"
img={artist.image} title={artist.name}
id={artist.id} img={artist.image}
musicbrainzId={artist.musicbrainz_id} id={artist.id}
imgItemId={artist.id} musicbrainzId={artist.musicbrainz_id}
mergeFunc={mergeArtists} imgItemId={artist.id}
mergeCleanerFunc={(r, id) => { mergeFunc={mergeArtists}
r.albums = [] mergeCleanerFunc={(r, id) => {
r.tracks = [] r.albums = [];
for (let i = 0; i < r.artists.length; i ++) { r.tracks = [];
if (r.artists[i].id === id) { for (let i = 0; i < r.artists.length; i++) {
delete r.artists[i] if (r.artists[i].id === id) {
} delete r.artists[i];
} }
return r }
}} return r;
subContent={<div className="flex flex-col gap-2 items-start"> }}
{artist.listen_count && <p>{artist.listen_count} play{ artist.listen_count > 1 ? 's' : ''}</p>} subContent={
{<p title={Math.floor(artist.time_listened / 60) + " minutes"}>{timeListenedString(artist.time_listened)}</p>} <div className="flex flex-col gap-2 items-start">
{<p title={new Date(artist.first_listen * 1000).toLocaleString()}>Listening since {new Date(artist.first_listen * 1000).toLocaleDateString()}</p>} {artist.listen_count && (
</div>} <p>
> {artist.listen_count} play{artist.listen_count > 1 ? "s" : ""}
<div className="mt-10"> </p>
<PeriodSelector setter={setPeriod} current={period} /> )}
{
<p title={Math.floor(artist.time_listened / 60 / 60) + " hours"}>
{timeListenedString(artist.time_listened)}
</p>
}
{
<p title={new Date(artist.first_listen * 1000).toLocaleString()}>
Listening since{" "}
{new Date(artist.first_listen * 1000).toLocaleDateString()}
</p>
}
</div> </div>
<div className="flex flex-col gap-20"> }
<div className="flex gap-15 mt-10 flex-wrap"> >
<LastPlays limit={20} artistId={artist.id} /> <div className="mt-10">
<TopTracks limit={8} period={period} artistId={artist.id} /> <PeriodSelector setter={setPeriod} current={period} />
<ActivityGrid configurable artistId={artist.id} /> </div>
</div> <div className="flex flex-col gap-20">
<ArtistAlbums period={period} artistId={artist.id} name={artist.name} /> <div className="flex gap-15 mt-10 flex-wrap">
<LastPlays limit={20} artistId={artist.id} />
<TopTracks limit={8} period={period} artistId={artist.id} />
<ActivityGrid configurable artistId={artist.id} />
</div> </div>
<ArtistAlbums period={period} artistId={artist.id} name={artist.name} />
</div>
</MediaLayout> </MediaLayout>
); );
} }

@ -8,55 +8,73 @@ import ActivityGrid from "~/components/ActivityGrid";
import { timeListenedString } from "~/utils/utils"; import { timeListenedString } from "~/utils/utils";
export async function clientLoader({ params }: LoaderFunctionArgs) { export async function clientLoader({ params }: LoaderFunctionArgs) {
let res = await fetch(`/apis/web/v1/track?id=${params.id}`); let res = await fetch(`/apis/web/v1/track?id=${params.id}`);
if (!res.ok) { if (!res.ok) {
throw new Response("Failed to load track", { status: res.status }); throw new Response("Failed to load track", { status: res.status });
} }
const track: Track = await res.json(); const track: Track = await res.json();
res = await fetch(`/apis/web/v1/album?id=${track.album_id}`) res = await fetch(`/apis/web/v1/album?id=${track.album_id}`);
if (!res.ok) { if (!res.ok) {
throw new Response("Failed to load album for track", { status: res.status }) throw new Response("Failed to load album for track", {
} status: res.status,
const album: Album = await res.json() });
return {track: track, album: album}; }
const album: Album = await res.json();
return { track: track, album: album };
} }
export default function Track() { export default function Track() {
const { track, album } = useLoaderData(); const { track, album } = useLoaderData();
const [period, setPeriod] = useState('week') const [period, setPeriod] = useState("week");
return ( return (
<MediaLayout type="Track" <MediaLayout
title={track.title} type="Track"
img={track.image} title={track.title}
id={track.id} img={track.image}
musicbrainzId={album.musicbrainz_id} id={track.id}
imgItemId={track.album_id} musicbrainzId={album.musicbrainz_id}
mergeFunc={mergeTracks} imgItemId={track.album_id}
mergeCleanerFunc={(r, id) => { mergeFunc={mergeTracks}
r.albums = [] mergeCleanerFunc={(r, id) => {
r.artists = [] r.albums = [];
for (let i = 0; i < r.tracks.length; i ++) { r.artists = [];
if (r.tracks[i].id === id) { for (let i = 0; i < r.tracks.length; i++) {
delete r.tracks[i] if (r.tracks[i].id === id) {
} delete r.tracks[i];
} }
return r }
}} return r;
subContent={<div className="flex flex-col gap-2 items-start"> }}
<Link to={`/album/${track.album_id}`}>appears on {album.title}</Link> subContent={
{track.listen_count && <p>{track.listen_count} play{ track.listen_count > 1 ? 's' : ''}</p>} <div className="flex flex-col gap-2 items-start">
{<p title={Math.floor(track.time_listened / 60) + " minutes"}>{timeListenedString(track.time_listened)}</p>} <Link to={`/album/${track.album_id}`}>appears on {album.title}</Link>
{<p title={new Date(track.first_listen * 1000).toLocaleString()}>Listening since {new Date(track.first_listen * 1000).toLocaleDateString()}</p>} {track.listen_count && (
</div>} <p>
> {track.listen_count} play{track.listen_count > 1 ? "s" : ""}
<div className="mt-10"> </p>
<PeriodSelector setter={setPeriod} current={period} /> )}
</div> {
<div className="flex flex-wrap gap-20 mt-10"> <p title={Math.floor(track.time_listened / 60 / 60) + " hours"}>
<LastPlays limit={20} trackId={track.id}/> {timeListenedString(track.time_listened)}
<ActivityGrid trackId={track.id} configurable /> </p>
</div> }
</MediaLayout> {
) <p title={new Date(track.first_listen * 1000).toLocaleString()}>
Listening since{" "}
{new Date(track.first_listen * 1000).toLocaleDateString()}
</p>
}
</div>
}
>
<div className="mt-10">
<PeriodSelector setter={setPeriod} current={period} />
</div>
<div className="flex flex-wrap gap-20 mt-10">
<LastPlays limit={20} trackId={track.id} />
<ActivityGrid trackId={track.id} configurable />
</div>
</MediaLayout>
);
} }

@ -1,102 +1,108 @@
import Timeframe from "~/types/timeframe" import Timeframe from "~/types/timeframe";
const timeframeToInterval = (timeframe: Timeframe): string => { const timeframeToInterval = (timeframe: Timeframe): string => {
switch (timeframe) { switch (timeframe) {
case Timeframe.Day: case Timeframe.Day:
return "1 day" return "1 day";
case Timeframe.Week: case Timeframe.Week:
return "1 week" return "1 week";
case Timeframe.Month: case Timeframe.Month:
return "1 month" return "1 month";
case Timeframe.Year: case Timeframe.Year:
return "1 year" return "1 year";
case Timeframe.AllTime: case Timeframe.AllTime:
return "99 years" return "99 years";
} }
} };
function timeSince(date: Date) { function timeSince(date: Date) {
const now = new Date(); const now = new Date();
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const intervals = [ const intervals = [
{ label: 'year', seconds: 31536000 }, { label: "year", seconds: 31536000 },
{ label: 'month', seconds: 2592000 }, { label: "month", seconds: 2592000 },
{ label: 'week', seconds: 604800 }, { label: "week", seconds: 604800 },
{ label: 'day', seconds: 86400 }, { label: "day", seconds: 86400 },
{ label: 'hour', seconds: 3600 }, { label: "hour", seconds: 3600 },
{ label: 'minute', seconds: 60 }, { label: "minute", seconds: 60 },
{ label: 'second', seconds: 1 }, { label: "second", seconds: 1 },
]; ];
for (const interval of intervals) { for (const interval of intervals) {
const count = Math.floor(seconds / interval.seconds); const count = Math.floor(seconds / interval.seconds);
if (count >= 1) { if (count >= 1) {
return `${count} ${interval.label}${count !== 1 ? 's' : ''} ago`; return `${count} ${interval.label}${count !== 1 ? "s" : ""} ago`;
}
} }
}
return 'just now';
return "just now";
} }
export { timeSince } export { timeSince };
type hsl = { type hsl = {
h: number, h: number;
s: number, s: number;
l: number, l: number;
} };
const hexToHSL = (hex: string): hsl => { const hexToHSL = (hex: string): hsl => {
let r = 0, g = 0, b = 0; let r = 0,
hex = hex.replace('#', ''); g = 0,
b = 0;
if (hex.length === 3) { hex = hex.replace("#", "");
r = parseInt(hex[0] + hex[0], 16);
g = parseInt(hex[1] + hex[1], 16); if (hex.length === 3) {
b = parseInt(hex[2] + hex[2], 16); r = parseInt(hex[0] + hex[0], 16);
} else if (hex.length === 6) { g = parseInt(hex[1] + hex[1], 16);
r = parseInt(hex.substring(0, 2), 16); b = parseInt(hex[2] + hex[2], 16);
g = parseInt(hex.substring(2, 4), 16); } else if (hex.length === 6) {
b = parseInt(hex.substring(4, 6), 16); r = parseInt(hex.substring(0, 2), 16);
} g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
r /= 255; }
g /= 255;
b /= 255; r /= 255;
g /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b); b /= 255;
let h = 0, s = 0, l = (max + min) / 2;
const max = Math.max(r, g, b),
if (max !== min) { min = Math.min(r, g, b);
const d = max - min; let h = 0,
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); s = 0,
switch (max) { l = (max + min) / 2;
case r: h = ((g - b) / d + (g < b ? 6 : 0)); break;
case g: h = ((b - r) / d + 2); break; if (max !== min) {
case b: h = ((r - g) / d + 4); break; const d = max - min;
} s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
h /= 6; switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
} }
h /= 6;
return { }
h: Math.round(h * 360),
s: Math.round(s * 100), return {
l: Math.round(l * 100) h: Math.round(h * 360),
}; s: Math.round(s * 100),
l: Math.round(l * 100),
};
}; };
const timeListenedString = (seconds: number) => { const timeListenedString = (seconds: number) => {
if (!seconds) return "" if (!seconds) return "";
if (seconds > (120 * 60) - 1) { let minutes = Math.floor(seconds / 60);
let hours = Math.floor(seconds / 60 / 60) return `${minutes} minutes listened`;
return `${hours} hours listened` };
} else {
let minutes = Math.floor(seconds / 60)
return `${minutes} minutes listened`
}
}
export {hexToHSL, timeListenedString} export { hexToHSL, timeListenedString };
export type {hsl} export type { hsl };

Loading…
Cancel
Save