fix: use minutes instead of hours for time listened

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

@ -19,12 +19,13 @@ 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
type="Album"
title={album.title} title={album.title}
img={album.image} img={album.image}
id={album.id} id={album.id}
@ -32,20 +33,35 @@ export default function Album() {
imgItemId={album.id} imgItemId={album.id}
mergeFunc={mergeAlbums} mergeFunc={mergeAlbums}
mergeCleanerFunc={(r, id) => { mergeCleanerFunc={(r, id) => {
r.artists = [] r.artists = [];
r.tracks = [] r.tracks = [];
for (let i = 0; i < r.albums.length; i ++) { for (let i = 0; i < r.albums.length; i++) {
if (r.albums[i].id === id) { if (r.albums[i].id === id) {
delete r.albums[i] delete r.albums[i];
} }
} }
return r return r;
}} }}
subContent={<div className="flex flex-col gap-2 items-start"> subContent={
{album.listen_count && <p>{album.listen_count} play{ album.listen_count > 1 ? 's' : ''}</p>} <div className="flex flex-col gap-2 items-start">
{<p title={Math.floor(album.time_listened / 60) + " minutes"}>{timeListenedString(album.time_listened)}</p>} {album.listen_count && (
{<p title={new Date(album.first_listen * 1000).toLocaleString()}>Listening since {new Date(album.first_listen * 1000).toLocaleDateString()}</p>} <p>
</div>} {album.listen_count} play{album.listen_count > 1 ? "s" : ""}
</p>
)}
{
<p title={Math.floor(album.time_listened / 60 / 60) + " hours"}>
{timeListenedString(album.time_listened)}
</p>
}
{
<p title={new Date(album.first_listen * 1000).toLocaleString()}>
Listening since{" "}
{new Date(album.first_listen * 1000).toLocaleDateString()}
</p>
}
</div>
}
> >
<div className="mt-10"> <div className="mt-10">
<PeriodSelector setter={setPeriod} current={period} /> <PeriodSelector setter={setPeriod} current={period} />

@ -20,17 +20,18 @@ 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
type="Artist"
title={artist.name} title={artist.name}
img={artist.image} img={artist.image}
id={artist.id} id={artist.id}
@ -38,20 +39,35 @@ export default function Artist() {
imgItemId={artist.id} imgItemId={artist.id}
mergeFunc={mergeArtists} mergeFunc={mergeArtists}
mergeCleanerFunc={(r, id) => { mergeCleanerFunc={(r, id) => {
r.albums = [] r.albums = [];
r.tracks = [] r.tracks = [];
for (let i = 0; i < r.artists.length; i ++) { for (let i = 0; i < r.artists.length; i++) {
if (r.artists[i].id === id) { if (r.artists[i].id === id) {
delete r.artists[i] delete r.artists[i];
} }
} }
return r return r;
}} }}
subContent={<div className="flex flex-col gap-2 items-start"> subContent={
{artist.listen_count && <p>{artist.listen_count} play{ artist.listen_count > 1 ? 's' : ''}</p>} <div className="flex flex-col gap-2 items-start">
{<p title={Math.floor(artist.time_listened / 60) + " minutes"}>{timeListenedString(artist.time_listened)}</p>} {artist.listen_count && (
{<p title={new Date(artist.first_listen * 1000).toLocaleString()}>Listening since {new Date(artist.first_listen * 1000).toLocaleDateString()}</p>} <p>
</div>} {artist.listen_count} play{artist.listen_count > 1 ? "s" : ""}
</p>
)}
{
<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 className="mt-10"> <div className="mt-10">
<PeriodSelector setter={setPeriod} current={period} /> <PeriodSelector setter={setPeriod} current={period} />

@ -13,20 +13,23 @@ export async function clientLoader({ params }: LoaderFunctionArgs) {
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() const album: Album = await res.json();
return {track: track, album: album}; 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
type="Track"
title={track.title} title={track.title}
img={track.image} img={track.image}
id={track.id} id={track.id}
@ -34,29 +37,44 @@ export default function Track() {
imgItemId={track.album_id} imgItemId={track.album_id}
mergeFunc={mergeTracks} mergeFunc={mergeTracks}
mergeCleanerFunc={(r, id) => { mergeCleanerFunc={(r, id) => {
r.albums = [] r.albums = [];
r.artists = [] r.artists = [];
for (let i = 0; i < r.tracks.length; i ++) { for (let i = 0; i < r.tracks.length; i++) {
if (r.tracks[i].id === id) { if (r.tracks[i].id === id) {
delete r.tracks[i] delete r.tracks[i];
} }
} }
return r return r;
}} }}
subContent={<div className="flex flex-col gap-2 items-start"> subContent={
<div className="flex flex-col gap-2 items-start">
<Link to={`/album/${track.album_id}`}>appears on {album.title}</Link> <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>} {track.listen_count && (
{<p title={Math.floor(track.time_listened / 60) + " minutes"}>{timeListenedString(track.time_listened)}</p>} <p>
{<p title={new Date(track.first_listen * 1000).toLocaleString()}>Listening since {new Date(track.first_listen * 1000).toLocaleDateString()}</p>} {track.listen_count} play{track.listen_count > 1 ? "s" : ""}
</div>} </p>
)}
{
<p title={Math.floor(track.time_listened / 60 / 60) + " hours"}>
{timeListenedString(track.time_listened)}
</p>
}
{
<p title={new Date(track.first_listen * 1000).toLocaleString()}>
Listening since{" "}
{new Date(track.first_listen * 1000).toLocaleDateString()}
</p>
}
</div>
}
> >
<div className="mt-10"> <div className="mt-10">
<PeriodSelector setter={setPeriod} current={period} /> <PeriodSelector setter={setPeriod} current={period} />
</div> </div>
<div className="flex flex-wrap gap-20 mt-10"> <div className="flex flex-wrap gap-20 mt-10">
<LastPlays limit={20} trackId={track.id}/> <LastPlays limit={20} trackId={track.id} />
<ActivityGrid trackId={track.id} configurable /> <ActivityGrid trackId={track.id} configurable />
</div> </div>
</MediaLayout> </MediaLayout>
) );
} }

@ -1,55 +1,57 @@
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;
hex = hex.replace("#", "");
if (hex.length === 3) { if (hex.length === 3) {
r = parseInt(hex[0] + hex[0], 16); r = parseInt(hex[0] + hex[0], 16);
@ -65,16 +67,25 @@ const hexToHSL = (hex: string): hsl => {
g /= 255; g /= 255;
b /= 255; b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b); const max = Math.max(r, g, b),
let h = 0, s = 0, l = (max + min) / 2; min = Math.min(r, g, b);
let h = 0,
s = 0,
l = (max + min) / 2;
if (max !== min) { if (max !== min) {
const d = max - min; const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) { switch (max) {
case r: h = ((g - b) / d + (g < b ? 6 : 0)); break; case r:
case g: h = ((b - r) / d + 2); break; h = (g - b) / d + (g < b ? 6 : 0);
case b: h = ((r - g) / d + 4); break; break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
} }
h /= 6; h /= 6;
} }
@ -82,21 +93,16 @@ const hexToHSL = (hex: string): hsl => {
return { return {
h: Math.round(h * 360), h: Math.round(h * 360),
s: Math.round(s * 100), s: Math.round(s * 100),
l: Math.round(l * 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