Compare commits
No commits in common. "32ba5fcff7373f1aeae46a1c6f87a89ce7b58a3e" and "8231c77ead7b762482b653d1b33c2a8136776173" have entirely different histories.
32ba5fcff7
...
8231c77ead
12 changed files with 15 additions and 210 deletions
|
|
@ -1,2 +0,0 @@
|
|||
LASTFM_API_KEY=
|
||||
LASTFM_USER=
|
||||
|
|
@ -33,9 +33,5 @@
|
|||
"typescript-eslint": "^8.47.0",
|
||||
"vite": "^7.2.2",
|
||||
"vite-plugin-devtools-json": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"lastfm-ts-api": "^2.2.0",
|
||||
"node-cache": "^5.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
|
|
@ -7,13 +7,6 @@ settings:
|
|||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
lastfm-ts-api:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
node-cache:
|
||||
specifier: ^5.1.2
|
||||
version: 5.1.2
|
||||
devDependencies:
|
||||
'@eslint/compat':
|
||||
specifier: ^1.4.0
|
||||
|
|
@ -686,10 +679,6 @@ packages:
|
|||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
clone@2.1.2:
|
||||
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -928,9 +917,6 @@ packages:
|
|||
known-css-properties@0.37.0:
|
||||
resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==}
|
||||
|
||||
lastfm-ts-api@2.2.0:
|
||||
resolution: {integrity: sha512-RGceA84ImKtPtFKZaAE9FbTI9hchjxAwi3ZJAr5nlrfVjvSQvpoEFtamrgJY+JYY9PdJusHil8eki00ll4ILZg==}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -1048,10 +1034,6 @@ packages:
|
|||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
node-cache@5.1.2:
|
||||
resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -1819,8 +1801,6 @@ snapshots:
|
|||
dependencies:
|
||||
clsx: 2.1.1
|
||||
|
||||
clone@2.1.2: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
|
|
@ -2067,8 +2047,6 @@ snapshots:
|
|||
|
||||
known-css-properties@0.37.0: {}
|
||||
|
||||
lastfm-ts-api@2.2.0: {}
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
|
@ -2155,10 +2133,6 @@ snapshots:
|
|||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
node-cache@5.1.2:
|
||||
dependencies:
|
||||
clone: 2.1.2
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
|
|
@ -266,7 +266,7 @@ Metro-like tile. Must be in a group to display correctly.
|
|||
grid-column-start: {column};
|
||||
"
|
||||
onmousemove={active ? updateDynamicMouseValues : null}
|
||||
href={active ? link : "#"}
|
||||
href="#h"
|
||||
>
|
||||
<!-- Tile pages -->
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
<!--
|
||||
@component
|
||||
Music tile.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { RecentTrack } from "$lib/server/clients/last-fm";
|
||||
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
|
||||
import Tile from "../common/Tile.svelte";
|
||||
import TileImagePage from "../pages/TileImagePage.svelte";
|
||||
import TileTextPage from "../pages/TileTextPage.svelte";
|
||||
|
||||
import iconLastFm from "$lib/assets/icons/last-fm.webp";
|
||||
import TileIconPage from "../pages/TileIconPage.svelte";
|
||||
|
||||
/**
|
||||
* Update interval delay (in milliseconds).
|
||||
*/
|
||||
const UPDATE_INTERVAL_DELAY = 1 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Link to the Last.FM website.
|
||||
*/
|
||||
const LASTFM_URL = "https://last.fm";
|
||||
|
||||
/**
|
||||
* Properties.
|
||||
*/
|
||||
let {
|
||||
row,
|
||||
column,
|
||||
size,
|
||||
track: initialTrack,
|
||||
}: {
|
||||
row: number;
|
||||
column: number;
|
||||
size: "small" | "medium" | "wide" | "large";
|
||||
track?: RecentTrack;
|
||||
} = $props();
|
||||
|
||||
/**
|
||||
* Current track data.
|
||||
*/
|
||||
let track = $state(initialTrack);
|
||||
|
||||
/**
|
||||
* Update interval.
|
||||
*/
|
||||
let updateInterval: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* Fetch the current track and put it in state.
|
||||
*/
|
||||
async function updateTrack() {
|
||||
const response = await fetch("/api/music");
|
||||
|
||||
if (!response.ok) {
|
||||
return; // NOTE: if non-ok status, don't update
|
||||
}
|
||||
|
||||
track = await response.json();
|
||||
}
|
||||
|
||||
// NOTE: update interval set/clear functions
|
||||
onMount(() => {
|
||||
updateInterval = setInterval(updateTrack, UPDATE_INTERVAL_DELAY);
|
||||
});
|
||||
|
||||
onDestroy(() => clearInterval(updateInterval));
|
||||
</script>
|
||||
|
||||
<Tile {row} {column} {size} icon={iconLastFm} link={track?.url ?? LASTFM_URL}>
|
||||
{#if track}
|
||||
<TileImagePage image={track.cover} />
|
||||
<TileTextPage
|
||||
title={track.title}
|
||||
subtitle={track.artist}
|
||||
text={track.album}
|
||||
/>
|
||||
{:else}
|
||||
<TileIconPage name="Last.FM" />
|
||||
{/if}
|
||||
</Tile>
|
||||
|
|
@ -20,14 +20,14 @@ Text page for a tile. Must be in a tile to display correctly.
|
|||
|
||||
<div class="w-full flex flex-col" style="height: var(--tile-page-height);">
|
||||
<div class="w-full flex flex-col gap-1 p-2 select-none text-white">
|
||||
<h1 class="font-bold text-md truncate">{title}</h1>
|
||||
<h1 class="font-bold text-md">{title}</h1>
|
||||
|
||||
{#if subtitle}
|
||||
<h2 class="text-md truncate">{subtitle}</h2>
|
||||
<h2 class="text-md">{subtitle}</h2>
|
||||
{/if}
|
||||
|
||||
{#if text}
|
||||
<p class="text-sm truncate">{text}</p>
|
||||
<p class="text-sm">{text}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
import { LASTFM_API_KEY, LASTFM_USER } from "$env/static/private";
|
||||
import { LastFMUser } from "lastfm-ts-api";
|
||||
|
||||
/**
|
||||
* Last.fm API client.
|
||||
*/
|
||||
const client = new LastFMUser(LASTFM_API_KEY);
|
||||
|
||||
/**
|
||||
* Recent track.
|
||||
*/
|
||||
export interface RecentTrack {
|
||||
/**
|
||||
* Song title.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Song artist.
|
||||
*/
|
||||
artist: string;
|
||||
|
||||
/**
|
||||
* Album title.
|
||||
*/
|
||||
album: string;
|
||||
|
||||
/**
|
||||
* Cover URL.
|
||||
*/
|
||||
cover: string;
|
||||
|
||||
/**
|
||||
* Song URL.
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent track from a listening history.
|
||||
* @returns Recent track.
|
||||
*/
|
||||
export async function getRecentTrack(): Promise<RecentTrack> {
|
||||
// NOTE: fetch the recent tracks
|
||||
const {
|
||||
recenttracks: {
|
||||
track: tracks,
|
||||
},
|
||||
} = await client.getRecentTracks({
|
||||
user: LASTFM_USER,
|
||||
});
|
||||
|
||||
// NOTE: get the first track and return it
|
||||
const track = tracks.shift()!;
|
||||
return {
|
||||
title: track.name,
|
||||
artist: track.artist["#text"],
|
||||
album: track.album["#text"],
|
||||
|
||||
// NOTE: the most HQ cover is always last in an array
|
||||
cover: track.image.pop()!["#text"],
|
||||
url: track.url,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import type { PageServerLoad } from './$types';
|
||||
|
||||
import { getRecentTrack } from '$lib/server/clients/last-fm';
|
||||
|
||||
export const load = (async () => {
|
||||
return { track: await getRecentTrack() };
|
||||
}) satisfies PageServerLoad;
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
<script lang="ts">
|
||||
import type { PageProps } from "./$types";
|
||||
|
||||
<script>
|
||||
import iconSynzr from "$lib/assets/icons/synzr.webp";
|
||||
|
||||
import imageTestRectangle from "$lib/assets/images/test-rectangle.webp";
|
||||
|
|
@ -8,12 +6,9 @@
|
|||
|
||||
import Tile from "$lib/components/tiles/common/Tile.svelte";
|
||||
import TileGroup from "$lib/components/tiles/common/TileGroup.svelte";
|
||||
import MusicTile from "$lib/components/tiles/music/MusicTile.svelte";
|
||||
import TileIconPage from "$lib/components/tiles/pages/TileIconPage.svelte";
|
||||
import TileImagePage from "$lib/components/tiles/pages/TileImagePage.svelte";
|
||||
import TileTextPage from "$lib/components/tiles/pages/TileTextPage.svelte";
|
||||
|
||||
const { data }: PageProps = $props();
|
||||
</script>
|
||||
|
||||
<TileGroup title="a lot of mikhails" rows={3} columns={3}>
|
||||
|
|
@ -28,7 +23,16 @@
|
|||
<TileTextPage title="Title" subtitle="Subtitle" text="Text" />
|
||||
</Tile>
|
||||
|
||||
<MusicTile row={3} column={5} size="medium" track={data.track} />
|
||||
<Tile
|
||||
size="medium"
|
||||
row={3}
|
||||
column={5}
|
||||
icon={iconSynzr}
|
||||
link="https://example.com"
|
||||
>
|
||||
<TileImagePage image={imageTestSquare} />
|
||||
<TileTextPage title="Title" subtitle="Subtitle" text="Text" />
|
||||
</Tile>
|
||||
|
||||
<Tile
|
||||
size="wide"
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
import { getRecentTrack } from '$lib/server/clients/last-fm';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
return Response.json(
|
||||
await getRecentTrack(),
|
||||
{
|
||||
headers: { 'cache-control': 'maxage=60' },
|
||||
},
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue