From 4d8f7c2b024709d93eb9285ec636ac9f224b6c6f Mon Sep 17 00:00:00 2001 From: apt-get Date: Sun, 15 Mar 2026 14:50:56 +0100 Subject: [PATCH] add types --- bun.lock | 5 +- src/lib/database.js | 36 ---------- src/lib/database.ts | 45 ++++++++++++ .../{stores.svelte.js => stores.svelte.ts} | 21 +++--- src/lib/types.ts | 19 +++++ src/lib/utils.js | 37 ---------- src/lib/utils.ts | 38 ++++++++++ src/routes/streams/Sidebar.svelte | 37 +++++----- src/routes/streams/WelcomePage.svelte | 2 +- src/routes/streams/[stream_id]/+page.svelte | 3 +- src/routes/streams/[stream_id]/Player.svelte | 12 ++-- .../streams/[stream_id]/StreamPage.svelte | 72 ++++++++++--------- 12 files changed, 183 insertions(+), 144 deletions(-) delete mode 100644 src/lib/database.js create mode 100644 src/lib/database.ts rename src/lib/{stores.svelte.js => stores.svelte.ts} (71%) create mode 100644 src/lib/types.ts delete mode 100644 src/lib/utils.js create mode 100644 src/lib/utils.ts diff --git a/bun.lock b/bun.lock index 1057d4a..0abb44c 100644 --- a/bun.lock +++ b/bun.lock @@ -1,10 +1,12 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "strimserve", "dependencies": { "@sveltejs/kit": "^2.17.1", + "@types/bun": "^1.2.2", "@types/node": "^20.17.17", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", @@ -27,7 +29,6 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^4.0.4", - "@types/bun": "^1.2.2", "svelte-adapter-bun": "https://github.com/gornostay25/svelte-adapter-bun", "typescript-svelte-plugin": "^0.3.45", }, @@ -733,7 +734,7 @@ "svelte": ["svelte@5.19.9", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-860s752/ZZxHIsii31ELkdKBOCeAuDsfb/AGUXJyQyzUVLRSt4oqEw/BV5+2+mNg8mbqmD3OK+vMvwWMPM6f8A=="], - "svelte-adapter-bun": ["svelte-adapter-bun@github:gornostay25/svelte-adapter-bun#ade8792", { "dependencies": { "tiny-glob": "^0.2.9" } }, "gornostay25-svelte-adapter-bun-ade8792"], + "svelte-adapter-bun": ["svelte-adapter-bun@github:gornostay25/svelte-adapter-bun#ade8792", { "dependencies": { "tiny-glob": "^0.2.9" } }, "gornostay25-svelte-adapter-bun-ade8792", "sha512-eZOxFn47qOyyqeFa8Qtd5eUhkhsT2yy+BWV6vxnsTs0Dv/Q1dO19ekQ7MrgR2lc6m9GWF15qHxtgyVzFyb040w=="], "svelte-check": ["svelte-check@4.1.4", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw=="], diff --git a/src/lib/database.js b/src/lib/database.js deleted file mode 100644 index 7b1e404..0000000 --- a/src/lib/database.js +++ /dev/null @@ -1,36 +0,0 @@ -import { Database } from 'bun:sqlite'; - -const dbName = './db/strimserve.db'; - -// Create a new database object and initialize it with the schema -const db = new Database(dbName); - -export function getStreams() { - const indexData = db - .prepare( - 'SELECT id, stream_date, title, tags, length_seconds ' + 'FROM Stream ORDER BY id DESC' - ) - .all(); - indexData.forEach((stream) => { - stream['stream_date'] = Date.parse(stream['stream_date']); - stream['tags'] = JSON.parse(stream['tags']); - }); - return indexData; -} - -export function getStreamInfo(streamId) { - const streamData = db - .prepare( - 'SELECT id, stream_date, filename, ' + - 'format, title, description, tags, ' + - 'length_seconds, tracks FROM Stream ' + - 'WHERE id = ?' - ) - .get(streamId); - if (streamData) { - streamData['stream_date'] = Date.parse(streamData['stream_date']); - streamData['tracks'] = JSON.parse(streamData['tracks']); - streamData['tags'] = JSON.parse(streamData['tags']); - } - return streamData; -} diff --git a/src/lib/database.ts b/src/lib/database.ts new file mode 100644 index 0000000..f787d27 --- /dev/null +++ b/src/lib/database.ts @@ -0,0 +1,45 @@ +import { Database } from 'bun:sqlite'; +import type { Stream, StreamSummary } from './types.js'; + +const dbName = './db/strimserve.db'; + +// Create a new database object and initialize it with the schema +const db = new Database(dbName); + +export function getStreams(): StreamSummary[] { + const indexData = db + .prepare( + 'SELECT id, stream_date, title, tags, length_seconds ' + 'FROM Stream ORDER BY id DESC' + ) + .all() as Record[]; + return indexData.map((stream) => ({ + id: stream.id as string, + stream_date: Date.parse(stream.stream_date as string), + title: stream.title as string | null, + tags: JSON.parse(stream.tags as string) as string[], + length_seconds: stream.length_seconds as number + })); +} + +export function getStreamInfo(streamId: string): Stream | null { + const streamData = db + .prepare( + 'SELECT id, stream_date, filename, ' + + 'format, title, description, tags, ' + + 'length_seconds, tracks FROM Stream ' + + 'WHERE id = ?' + ) + .get(streamId) as Record | null; + if (!streamData) return null; + return { + id: streamData.id as string, + stream_date: Date.parse(streamData.stream_date as string), + filename: streamData.filename as string, + format: streamData.format as string, + title: streamData.title as string | null, + description: streamData.description as string | null, + tags: JSON.parse(streamData.tags as string) as string[], + length_seconds: streamData.length_seconds as number, + tracks: JSON.parse(streamData.tracks as string) + }; +} diff --git a/src/lib/stores.svelte.js b/src/lib/stores.svelte.ts similarity index 71% rename from src/lib/stores.svelte.js rename to src/lib/stores.svelte.ts index a0df23f..f7cb693 100644 --- a/src/lib/stores.svelte.js +++ b/src/lib/stores.svelte.ts @@ -1,11 +1,12 @@ import { writable } from 'svelte/store'; import { browser } from '$app/environment'; import { SvelteSet } from 'svelte/reactivity'; +import type { Stream } from '$lib/types'; -export const currentStream = writable({}); -export const currentSongIndex = writable(null); +export const currentStream = writable(null); +export const currentSongIndex = writable(null); -export const favoritedStreams = new SvelteSet( +export const favoritedStreams = new SvelteSet( JSON.parse((browser && localStorage.getItem('favoritedStreams')) || '[]') ); $effect.root(() => { @@ -42,14 +43,14 @@ export const tagList = [ 'uplifting' ]; -let timestampList; +let timestampList: number[]; // utility -function locationOf(element, array, start, end) { +function locationOf(element: number, array: number[], start?: number, end?: number): number { start = start || 0; end = end || array.length; - var pivot = parseInt(start + (end - start) / 2, 10); + const pivot = parseInt(String(start + (end - start) / 2), 10); if (end - start <= 1 || array[pivot] === element) return pivot; if (array[pivot] < element) { return locationOf(element, array, pivot, end); @@ -60,13 +61,13 @@ function locationOf(element, array, start, end) { // exported methods -export function getSongAtTime(currentTime) { +export function getSongAtTime(currentTime: number): number { return locationOf(currentTime, timestampList) - 1; } // less operations needed when doing regular lookups -export function updateCurrentSong(currentTime, songIndex) { - function updateCurrentSongRecurse(songIndex) { +export function updateCurrentSong(currentTime: number, songIndex: number): void { + function updateCurrentSongRecurse(songIndex: number): number { if (currentTime >= timestampList[songIndex + 2]) { return updateCurrentSongRecurse(songIndex + 1); } else if (currentTime < timestampList[songIndex + 1]) { @@ -78,7 +79,7 @@ export function updateCurrentSong(currentTime, songIndex) { currentSongIndex.set(updateCurrentSongRecurse(songIndex)); } -export function updateCurrentStream(stream) { +export function updateCurrentStream(stream: Stream): void { currentStream.set(stream); timestampList = [-Infinity, ...stream.tracks.map((track) => track[0]), Infinity]; currentSongIndex.set(0); diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..8023eb6 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,19 @@ +/** A track entry: [timestamp_seconds, artist, title] */ +export type Track = [number, string, string]; + +/** The summary shape used in sidebar listings. */ +export interface StreamSummary { + id: string; + stream_date: number; + title: string | null; + tags: string[]; + length_seconds: number; +} + +/** The full stream shape used on the detail/player page. */ +export interface Stream extends StreamSummary { + filename: string; + format: string; + description: string | null; + tracks: Track[]; +} diff --git a/src/lib/utils.js b/src/lib/utils.js deleted file mode 100644 index 7b9a121..0000000 --- a/src/lib/utils.js +++ /dev/null @@ -1,37 +0,0 @@ -import Sqids from 'sqids'; - -let sqids = new Sqids({ minLength: 6, alphabet: 'abcdefghijklmnopqrstuvwxyz0123456789' }); - -export function hashcode(str) { - for (var i = 0, h = 9; i < str.length; ) h = Math.imul(h ^ str.charCodeAt(i++), 9 ** 9); - return h ^ (h >>> 9); -} - -// for mnemonic display -export function shorthandCode(str) { - return sqids.encode([hashcode(str) & 0xffff]); -} - -// for tag display -export function hashColor(str) { - const hash = hashcode(str); - return `hsl(${hash % 360}, ${65 + (hash % 30) + 1}%, ${85 + (hash % 10) + 1}%)`; -} - -export function formatSecondsToHms(s) { - s = Number(s); - var h = Math.floor(s / 3600); - var m = Math.ceil((s % 3600) / 60); - - var hDisplay = h > 0 ? h + (h == 1 ? ' hr' : ' hrs') + (m > 0 ? ', ' : '') : ''; - var mDisplay = m > 0 ? m + (m == 1 ? ' min' : ' mins') : ''; - return hDisplay + mDisplay; -} - -export function formatDate(unix_timestamp) { - return new Date(unix_timestamp).toISOString().split('T')[0]; -} - -export function formatTrackTime(s) { - return new Date(s * 1000).toISOString().slice(11, 19); -} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..7b6cdac --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,38 @@ +import Sqids from 'sqids'; + +const sqids = new Sqids({ minLength: 6, alphabet: 'abcdefghijklmnopqrstuvwxyz0123456789' }); + +export function hashcode(str: string): number { + let h = 9; + for (let i = 0; i < str.length; ) h = Math.imul(h ^ str.charCodeAt(i++), 9 ** 9); + return h ^ (h >>> 9); +} + +// for mnemonic display +export function shorthandCode(str: string): string { + return sqids.encode([hashcode(str) & 0xffff]); +} + +// for tag display +export function hashColor(str: string): string { + const hash = hashcode(str); + return `hsl(${hash % 360}, ${65 + (hash % 30) + 1}%, ${85 + (hash % 10) + 1}%)`; +} + +export function formatSecondsToHms(s: number): string { + s = Number(s); + const h = Math.floor(s / 3600); + const m = Math.ceil((s % 3600) / 60); + + const hDisplay = h > 0 ? h + (h == 1 ? ' hr' : ' hrs') + (m > 0 ? ', ' : '') : ''; + const mDisplay = m > 0 ? m + (m == 1 ? ' min' : ' mins') : ''; + return hDisplay + mDisplay; +} + +export function formatDate(unix_timestamp: number): string { + return new Date(unix_timestamp).toISOString().split('T')[0]; +} + +export function formatTrackTime(s: number): string { + return new Date(s * 1000).toISOString().slice(11, 19); +} diff --git a/src/routes/streams/Sidebar.svelte b/src/routes/streams/Sidebar.svelte index c15e11b..cc0f304 100644 --- a/src/routes/streams/Sidebar.svelte +++ b/src/routes/streams/Sidebar.svelte @@ -2,10 +2,11 @@ import { untrack } from 'svelte'; import { goto } from '$app/navigation'; import { currentStream, favoritedStreams } from '$lib/stores.svelte.js'; + import type { StreamSummary } from '$lib/types'; import { hashColor, shorthandCode, formatSecondsToHms, formatDate } from '$lib/utils.js'; import TagSelect from './TagSelect.svelte'; - let { streams } = $props(); + let { streams }: { streams: StreamSummary[] } = $props(); let filteredTags = $state([]); let favoritesOnly = $state(false); let listOpen = $state(); @@ -21,13 +22,13 @@ function streamsToDisplay() { return streams.filter( (stream) => - !('tags' in stream) || filteredTags.every((tag) => stream['tags'].includes(tag)) + !('tags' in stream) || filteredTags.every((tag) => stream.tags.includes(tag)) ); } function getRemainingTags() { - let tagCounts = displayedStreams.reduce((tags, stream) => { - stream['tags'].forEach((tag) => (tag in tags ? (tags[tag] += 1) : (tags[tag] = 1))); + let tagCounts = displayedStreams.reduce((tags: Record, stream) => { + stream.tags.forEach((tag) => (tags[tag] ? (tags[tag] += 1) : (tags[tag] = 1))); return tags; }, {}); return Object.entries(tagCounts) @@ -35,14 +36,14 @@ .map((el) => el[0]); } - function getDisplayedTagsInList(streamTags, remainingTags) { + function getDisplayedTagsInList(streamTags: string[], remainingTags: string[]) { return streamTags.filter((tag) => remainingTags.includes(tag)); } - function updateFavorites(stream) { - favoritedStreams.has(stream['id']) - ? favoritedStreams.delete(stream['id']) - : favoritedStreams.add(stream['id']); + function updateFavorites(stream: StreamSummary) { + favoritedStreams.has(stream.id) + ? favoritedStreams.delete(stream.id) + : favoritedStreams.add(stream.id); } @@ -56,28 +57,26 @@
    {#each streams as stream} - {@const favorited = favoritedStreams.has(stream['id'])} - {@const current = $currentStream['id'] === stream['id']} + {@const favorited = favoritedStreams.has(stream.id)} + {@const current = $currentStream?.id === stream.id}