fix: dont copy scheme in share

This commit is contained in:
dusk 2023-05-09 17:32:21 +03:00
parent 9a0d05df94
commit 54ddf32e21
Signed by: dusk
GPG Key ID: 1D8F8FAF2294D6EA
16 changed files with 397 additions and 382 deletions

View File

@ -1,4 +1,4 @@
{ {
"svelte.enable-ts-plugin": true, "svelte.enable-ts-plugin": true,
"editor.tabSize": 2 "editor.tabSize": 2
} }

View File

@ -17,4 +17,4 @@ A production build is also available at `packages.x86_64-linux.musikspider`.
- [ ] implement persistence of music data, playlists and such - [ ] implement persistence of music data, playlists and such
- [ ] implement scrobbling (last.fm, etc) - [ ] implement scrobbling (last.fm, etc)
- [ ] implement discord status - [ ] implement discord status
- [ ] add tauri app? - [ ] add tauri app?

View File

@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {}
}, }
} };

2
src/app.d.ts vendored
View File

@ -11,4 +11,4 @@ declare global {
} }
} }
export { }; export {};

View File

@ -1,15 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="dark" lang="en"> <html class="dark" lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<head> <body data-sveltekit-preload-data="hover" data-theme="crimson">
<meta charset="utf-8" /> <div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> </body>
<meta name="viewport" content="width=device-width" /> </html>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="crimson">
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
</body>
</html>

View File

@ -1,2 +1,7 @@
html, body { @apply h-full overflow-hidden; } html,
.card { @apply shadow shadow-black; } body {
@apply h-full overflow-hidden;
}
.card {
@apply shadow shadow-black;
}

View File

@ -1,174 +1,176 @@
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import type { TrackWithId } from "./types"; import type { TrackWithId } from './types';
const API_VERSION: number = 20; const API_VERSION: number = 20;
const HTTP_DISABLED_ERROR: string = const HTTP_DISABLED_ERROR: string =
"server does not have HTTP resources enabled, you will not be able to stream music"; 'server does not have HTTP resources enabled, you will not be able to stream music';
const SERVER_API_INCOMPATIBLE_ERROR: (serverApi: number) => string = const SERVER_API_INCOMPATIBLE_ERROR: (serverApi: number) => string = (serverApi) =>
(serverApi) => `server API version (${serverApi}) is different from our supported version (${API_VERSION})`; `server API version (${serverApi}) is different from our supported version (${API_VERSION})`;
interface Message { interface Message {
name: string; name: string;
id: string; id: string;
device_id: string; device_id: string;
type: MessageType; type: MessageType;
options: any; options: any;
}; }
type MessageType = 'request' | 'response' | 'broadcast'; type MessageType = 'request' | 'response' | 'broadcast';
type RequestCallback = (arg0: Message | null) => void; type RequestCallback = (arg0: Message | null) => void;
interface Callbacks { interface Callbacks {
onDisconnect: (authenticated: boolean, reason: string) => void; onDisconnect: (authenticated: boolean, reason: string) => void;
onConnect: (initial: Message) => void; onConnect: (initial: Message) => void;
onIncompatible: (reason: string) => void; onIncompatible: (reason: string) => void;
} }
export class MetadataCommunicator { export class MetadataCommunicator {
ws: WebSocket | null; ws: WebSocket | null;
deviceId: string; deviceId: string;
callbacks: Map<string, RequestCallback>; callbacks: Map<string, RequestCallback>;
authenticated: boolean; authenticated: boolean;
eventCallbacks: Callbacks; eventCallbacks: Callbacks;
onConnectCallbacks: (() => void)[]; onConnectCallbacks: (() => void)[];
constructor() { constructor() {
this.callbacks = new Map(); this.callbacks = new Map();
this.deviceId = crypto.randomUUID(); this.deviceId = crypto.randomUUID();
this.authenticated = false; this.authenticated = false;
this.eventCallbacks = { this.eventCallbacks = {
onDisconnect: () => { }, onDisconnect: () => {},
onConnect: () => { }, onConnect: () => {},
onIncompatible: () => { }, onIncompatible: () => {}
}; };
this.onConnectCallbacks = []; this.onConnectCallbacks = [];
this.ws = null; this.ws = null;
} }
setCallbacks(callbacks: Callbacks) { setCallbacks(callbacks: Callbacks) {
this.eventCallbacks = callbacks; this.eventCallbacks = callbacks;
} }
connect(address: string, password: string) { connect(address: string, password: string) {
this.close(); this.close();
const scheme = dev ? "ws" : "wss"; const scheme = dev ? 'ws' : 'wss';
this.ws = new WebSocket(`${scheme}://${address}`); this.ws = new WebSocket(`${scheme}://${address}`);
this.ws.addEventListener('open', (event) => { this.ws.addEventListener('open', (event) => {
this.makeRequest("authenticate", 'request', { password }, (msg) => { this.makeRequest('authenticate', 'request', { password }, (msg) => {
if (msg!.options.authenticated) { if (msg!.options.authenticated) {
this.authenticated = true; this.authenticated = true;
this.eventCallbacks.onConnect(msg!); this.eventCallbacks.onConnect(msg!);
this.onConnectCallbacks.forEach((f) => f()); this.onConnectCallbacks.forEach((f) => f());
this.onConnectCallbacks = []; this.onConnectCallbacks = [];
if (!msg!.options.environment.http_server_enabled) { if (!msg!.options.environment.http_server_enabled) {
this.eventCallbacks.onIncompatible(HTTP_DISABLED_ERROR); this.eventCallbacks.onIncompatible(HTTP_DISABLED_ERROR);
} }
const serverApiVersion = msg!.options.environment.api_version; const serverApiVersion = msg!.options.environment.api_version;
if (serverApiVersion != API_VERSION) { if (serverApiVersion != API_VERSION) {
this.eventCallbacks.onIncompatible(SERVER_API_INCOMPATIBLE_ERROR(serverApiVersion)); this.eventCallbacks.onIncompatible(SERVER_API_INCOMPATIBLE_ERROR(serverApiVersion));
} }
} }
}); });
}); });
this.ws.addEventListener('close', (event) => { this.ws.addEventListener('close', (event) => {
this.eventCallbacks.onDisconnect(this.authenticated, `${event.reason} (code ${event.code})`); this.eventCallbacks.onDisconnect(this.authenticated, `${event.reason} (code ${event.code})`);
this.authenticated = false; this.authenticated = false;
}); });
this.ws.addEventListener('message', (event) => { this.ws.addEventListener('message', (event) => {
const parsed: Message = JSON.parse(event.data); const parsed: Message = JSON.parse(event.data);
const maybeCallback = this.callbacks.get(parsed.id); const maybeCallback = this.callbacks.get(parsed.id);
if (maybeCallback) { if (maybeCallback) {
maybeCallback(parsed); maybeCallback(parsed);
this.callbacks.delete(parsed.id); this.callbacks.delete(parsed.id);
} }
}); });
} }
fetchTracksCount(): Promise<number> { fetchTracksCount(): Promise<number> {
const options = { count_only: true }; const options = { count_only: true };
const th = this; const th = this;
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
th.makeRequest("query_tracks", "request", options, (resp) => { th.makeRequest('query_tracks', 'request', options, (resp) => {
if (resp) { if (resp) {
resolve(resp.options.count); resolve(resp.options.count);
} else { } else {
reject(null); reject(null);
} }
}); });
}); });
} }
fetchTracks(limit: number, offset: number, filter: string | null = null): Promise<TrackWithId[]> { fetchTracks(limit: number, offset: number, filter: string | null = null): Promise<TrackWithId[]> {
const options: any = { limit, offset }; const options: any = { limit, offset };
if (filter !== null) options.filter = filter; if (filter !== null) options.filter = filter;
const th = this; const th = this;
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
th.makeRequest("query_tracks", "request", options, (resp) => { th.makeRequest('query_tracks', 'request', options, (resp) => {
if (resp) { if (resp) {
const data: any[] = resp.options.data; const data: any[] = resp.options.data;
resolve(data.map((t) => ({ resolve(
id: t.external_id, data.map((t) => ({
track: { id: t.external_id,
id: t.id, track: {
title: t.title, id: t.id,
track_num: t.track, title: t.title,
album_title: t.album, track_num: t.track,
album_id: t.album_id, album_title: t.album,
artist_name: t.artist, album_id: t.album_id,
artist_id: t.artist_id, artist_name: t.artist,
thumbnail_id: t.thumbnail_id, artist_id: t.artist_id,
} thumbnail_id: t.thumbnail_id
}))); }
} else { }))
reject(null); );
} } else {
}); reject(null);
}); }
} });
});
}
private makeRequest(name: string, type: MessageType, options: object, callback: RequestCallback) { private makeRequest(name: string, type: MessageType, options: object, callback: RequestCallback) {
// return if not authenticated, allow authentication messages // return if not authenticated, allow authentication messages
if (this.isClosed() || !this.authenticated && name != "authenticate") { if (this.isClosed() || (!this.authenticated && name != 'authenticate')) {
callback(null); callback(null);
return; return;
} }
// Unique enough for our purposes (as request ID) // Unique enough for our purposes (as request ID)
const id = Math.random().toString(36).substring(2) + Date.now().toString(36); const id = Math.random().toString(36).substring(2) + Date.now().toString(36);
this.callbacks.set(id, callback); this.callbacks.set(id, callback);
const payload = JSON.stringify({ const payload = JSON.stringify({
name, name,
type, type,
options, options,
device_id: this.deviceId, device_id: this.deviceId,
id, id
}); });
console.trace("sending metadata message: " + payload); console.trace('sending metadata message: ' + payload);
this.ws!.send(payload); this.ws!.send(payload);
} }
close() { close() {
if (this.isClosed()) return; if (this.isClosed()) return;
this.ws!.close(); this.ws!.close();
this.authenticated = false; this.authenticated = false;
this.callbacks.clear(); this.callbacks.clear();
} }
isClosed() { isClosed() {
return ( return (
this.ws === null this.ws === null ||
|| this.ws.readyState === WebSocket.CLOSED this.ws.readyState === WebSocket.CLOSED ||
|| this.ws.readyState === WebSocket.CLOSING this.ws.readyState === WebSocket.CLOSING
); );
} }
onConnect(cb: () => Promise<void>) { onConnect(cb: () => Promise<void>) {
if (!this.isClosed() && this.authenticated) { if (!this.isClosed() && this.authenticated) {
cb(); cb();
} else { } else {
this.onConnectCallbacks = [...this.onConnectCallbacks, cb]; this.onConnectCallbacks = [...this.onConnectCallbacks, cb];
} }
} }
} }

View File

@ -4,14 +4,13 @@
makeThumbnailUrl, makeThumbnailUrl,
currentTrack, currentTrack,
setQueuePositionTo, setQueuePositionTo,
makeGenScopedTokenUrl, makeGenScopedTokenUrl
makeShareUrl
} from '../stores'; } from '../stores';
import Spinnny from '~icons/line-md/loading-loop'; import Spinnny from '~icons/line-md/loading-loop';
import IconPlay from '~icons/mdi/play'; import IconPlay from '~icons/mdi/play';
import IconMusic from '~icons/mdi/music'; import IconMusic from '~icons/mdi/music';
import { toastStore } from '@skeletonlabs/skeleton'; import { toastStore } from '@skeletonlabs/skeleton';
import { getAudioElement } from '../utils'; import { getAudioElement, makeShareUrl } from '../utils';
export let track_with_id: TrackWithId; export let track_with_id: TrackWithId;
let track = track_with_id.track; let track = track_with_id.track;

View File

@ -1,5 +1,5 @@
import { MetadataCommunicator } from "../../comms"; import { MetadataCommunicator } from '../../comms';
export const _metadataComm = new MetadataCommunicator(); export const _metadataComm = new MetadataCommunicator();
export const ssr = false; export const ssr = false;
export const prerender = true; export const prerender = true;

View File

@ -1,2 +1,2 @@
export const ssr = false; export const ssr = false;
export const prerender = false; export const prerender = false;

View File

@ -3,17 +3,17 @@ import { LOCAL_MUSIKQUAD_SERVER } from '$env/static/private';
import { scheme } from '../../../utils'; import { scheme } from '../../../utils';
interface MusicInfo { interface MusicInfo {
title: string, title: string;
album: string, album: string;
artist: string, artist: string;
} }
export async function load({ params }) { export async function load({ params }) {
const resp = await fetch(`${LOCAL_MUSIKQUAD_SERVER}/share/info/${params.token}`); const resp = await fetch(`${LOCAL_MUSIKQUAD_SERVER}/share/info/${params.token}`);
const info: MusicInfo = await resp.json(); const info: MusicInfo = await resp.json();
return { return {
info, info,
thumbnail_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/thumbnail/${params.token}`, thumbnail_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/thumbnail/${params.token}`,
audio_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/audio/${params.token}`, audio_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/audio/${params.token}`
}; };
} }

View File

@ -1,55 +1,55 @@
import { get, writable } from 'svelte/store'; import { get, writable } from 'svelte/store';
import { type Track, type TrackId, type TrackWithId, LoopKind } from './types'; import { type Track, type TrackId, type TrackWithId, LoopKind } from './types';
import { PUBLIC_BASEURL, PUBLIC_MUSIKQUAD_SERVER } from '$env/static/public'; import { PUBLIC_MUSIKQUAD_SERVER } from '$env/static/public';
import { scheme } from './utils'; import { scheme } from './utils';
function writableStorage(key: string, defaultValue: string) { function writableStorage(key: string, defaultValue: string) {
const store = writable(localStorage.getItem(key) ?? defaultValue); const store = writable(localStorage.getItem(key) ?? defaultValue);
store.subscribe(value => localStorage.setItem(key, value)); store.subscribe((value) => localStorage.setItem(key, value));
return store; return store;
} }
export const address = writableStorage("address", PUBLIC_MUSIKQUAD_SERVER); export const address = writableStorage('address', PUBLIC_MUSIKQUAD_SERVER);
export const token = writableStorage("token", ""); export const token = writableStorage('token', '');
export function makeThumbnailUrl(id: number) { export function makeThumbnailUrl(id: number) {
if (id === 0) { if (id === 0) {
return null; return null;
} }
return `${scheme}://${get(address)}/thumbnail/${id}?token=${get(token)}`; return `${scheme}://${get(address)}/thumbnail/${id}?token=${get(token)}`;
} }
export function makeAudioUrl(id: TrackId) { export function makeAudioUrl(id: TrackId) {
return `${scheme}://${get(address)}/audio/external_id/${id}?token=${get(token)}`; return `${scheme}://${get(address)}/audio/external_id/${id}?token=${get(token)}`;
} }
export function makeGenScopedTokenUrl(id: TrackId) { export function makeGenScopedTokenUrl(id: TrackId) {
return `${scheme}://${get(address)}/share/generate/${id}?token=${get(token)}`; return `${scheme}://${get(address)}/share/generate/${id}?token=${get(token)}`;
}
export function makeShareUrl(token: string) {
return `${scheme}://${PUBLIC_BASEURL}/share/${token}`;
} }
export const currentTrack = writable<TrackWithId | null>(null); export const currentTrack = writable<TrackWithId | null>(null);
export function getCurrentTrack(tracks: Map<TrackId, Track>, queue: TrackId[], position: number | null): TrackWithId | null { export function getCurrentTrack(
if (position === null) { tracks: Map<TrackId, Track>,
return null; queue: TrackId[],
} position: number | null
const id = queue.at(position); ): TrackWithId | null {
if (id === undefined) { if (position === null) {
return null; return null;
} }
const track = tracks.get(id); const id = queue.at(position);
if (track === undefined) { if (id === undefined) {
return null; return null;
} }
return { const track = tracks.get(id);
track, if (track === undefined) {
id, return null;
}; }
return {
track,
id
};
} }
export const queuePosition = writable<number | null>(null); export const queuePosition = writable<number | null>(null);
@ -58,50 +58,53 @@ export const tracks = writable<Map<TrackId, Track>>(new Map());
export const tracksSorted = writable<TrackId[]>([]); export const tracksSorted = writable<TrackId[]>([]);
queuePosition.subscribe((pos) => currentTrack.set(getCurrentTrack(get(tracks), get(queue), pos))); queuePosition.subscribe((pos) => currentTrack.set(getCurrentTrack(get(tracks), get(queue), pos)));
tracks.subscribe((newTracks) => currentTrack.set(getCurrentTrack(newTracks, get(queue), get(queuePosition)))); tracks.subscribe((newTracks) =>
currentTrack.set(getCurrentTrack(newTracks, get(queue), get(queuePosition)))
);
export function setQueuePositionTo(track_id: TrackId) { export function setQueuePositionTo(track_id: TrackId) {
let q = get(queue); let q = get(queue);
const position = q.indexOf(track_id); const position = q.indexOf(track_id);
if (position !== -1) { if (position !== -1) {
queuePosition.set(position); queuePosition.set(position);
} else { } else {
q.push(track_id); q.push(track_id);
queue.set(q); queue.set(q);
queuePosition.set(q.length - 1); queuePosition.set(q.length - 1);
} }
} }
export function getPrevQueuePosition(respectLoop: boolean) { export function getPrevQueuePosition(respectLoop: boolean) {
const pos = get(queuePosition); const pos = get(queuePosition);
if (pos !== null) { if (pos !== null) {
const q = get(queue); const q = get(queue);
const l = get(loop); const l = get(loop);
const _newPos = pos - 1; const _newPos = pos - 1;
const newPos = _newPos > -1 ? _newPos : l === LoopKind.Once || !respectLoop ? q.length - 1 : null; const newPos =
return newPos; _newPos > -1 ? _newPos : l === LoopKind.Once || !respectLoop ? q.length - 1 : null;
} return newPos;
return null; }
return null;
} }
export function getNextQueuePosition(respectLoop: boolean) { export function getNextQueuePosition(respectLoop: boolean) {
const pos = get(queuePosition); const pos = get(queuePosition);
if (pos !== null) { if (pos !== null) {
const q = get(queue); const q = get(queue);
const l = get(loop); const l = get(loop);
const _newPos = pos + 1; const _newPos = pos + 1;
const newPos = _newPos < q.length ? _newPos : l === LoopKind.Once || !respectLoop ? 0 : null; const newPos = _newPos < q.length ? _newPos : l === LoopKind.Once || !respectLoop ? 0 : null;
return newPos; return newPos;
} }
return null; return null;
} }
export function prevQueuePosition(respectLoop: boolean = false) { export function prevQueuePosition(respectLoop: boolean = false) {
queuePosition.set(getPrevQueuePosition(respectLoop)); queuePosition.set(getPrevQueuePosition(respectLoop));
} }
export function nextQueuePosition(respectLoop: boolean = false) { export function nextQueuePosition(respectLoop: boolean = false) {
queuePosition.set(getNextQueuePosition(respectLoop)); queuePosition.set(getNextQueuePosition(respectLoop));
} }
export const paused = writable<boolean>(false); export const paused = writable<boolean>(false);
@ -110,51 +113,51 @@ export const muted = writable<boolean>(false);
export const loop = writable<LoopKind>(LoopKind.Off); export const loop = writable<LoopKind>(LoopKind.Off);
export function changeLoop() { export function changeLoop() {
switch (get(loop)) { switch (get(loop)) {
case LoopKind.Always: case LoopKind.Always:
loop.set(LoopKind.Off); loop.set(LoopKind.Off);
break; break;
case LoopKind.Off: case LoopKind.Off:
loop.set(LoopKind.Once); loop.set(LoopKind.Once);
break; break;
case LoopKind.Once: case LoopKind.Once:
loop.set(LoopKind.Always); loop.set(LoopKind.Always);
break; break;
} }
} }
export const searchText = writable<string>(""); export const searchText = writable<string>('');
export function search(q: string) { export function search(q: string) {
const query = q.trim(); const query = q.trim();
const t = get(tracks); const t = get(tracks);
if (query.length === 0) { if (query.length === 0) {
let result: TrackId[] = []; let result: TrackId[] = [];
t.forEach((_, id) => (result.push(id))); t.forEach((_, id) => result.push(id));
tracksSorted.set(result); tracksSorted.set(result);
return; return;
} }
const smartCase = query.toLowerCase() === query; const smartCase = query.toLowerCase() === query;
let result: TrackId[] = []; let result: TrackId[] = [];
t.forEach((track, id) => { t.forEach((track, id) => {
if (smartCase) { if (smartCase) {
const titleHas = track.title.toLowerCase().includes(query); const titleHas = track.title.toLowerCase().includes(query);
const albumHas = track.album_title.toLowerCase().includes(query); const albumHas = track.album_title.toLowerCase().includes(query);
const artistHas = track.artist_name.toLowerCase().includes(query); const artistHas = track.artist_name.toLowerCase().includes(query);
if (titleHas || albumHas || artistHas) { if (titleHas || albumHas || artistHas) {
result.push(id); result.push(id);
} }
} else { } else {
const titleHas = track.title.includes(query); const titleHas = track.title.includes(query);
const albumHas = track.album_title.includes(query); const albumHas = track.album_title.includes(query);
const artistHas = track.artist_name.includes(query); const artistHas = track.artist_name.includes(query);
if (titleHas || albumHas || artistHas) { if (titleHas || albumHas || artistHas) {
result.push(id); result.push(id);
} }
} }
}); });
tracksSorted.set(result); tracksSorted.set(result);
} }

View File

@ -2,32 +2,32 @@ export type ResourceId = bigint;
export type TrackId = string; export type TrackId = string;
export interface Track { export interface Track {
id: number, id: number;
title: string, title: string;
track_num: number, track_num: number;
album_title: string, album_title: string;
album_id: ResourceId, album_id: ResourceId;
artist_name: string, artist_name: string;
artist_id: ResourceId, artist_id: ResourceId;
thumbnail_id: number, thumbnail_id: number;
} }
export interface TrackWithId { export interface TrackWithId {
id: TrackId, id: TrackId;
track: Track, track: Track;
} }
export interface Artist { export interface Artist {
name: string, name: string;
} }
export interface Album { export interface Album {
title: string, title: string;
artist_id: ResourceId, artist_id: ResourceId;
} }
export enum LoopKind { export enum LoopKind {
Off, Off,
Once, Once,
Always, Always
} }

View File

@ -1,67 +1,80 @@
import { dev } from '$app/environment'; import { dev } from '$app/environment';
import { PUBLIC_BASEURL } from '$env/static/public';
export const scheme = dev ? "http" : "https"; export const scheme = dev ? 'http' : 'https';
export function makeShareUrl(token: string) {
return `${PUBLIC_BASEURL}/share/${token}`;
}
export function getAudioElement() { export function getAudioElement() {
const elem = document.getElementById('audio-source'); const elem = document.getElementById('audio-source');
if (elem === null) { if (elem === null) {
return null; return null;
} }
return elem as HTMLAudioElement; return elem as HTMLAudioElement;
} }
export function calculateMinuteSecond(seconds: number) { export function calculateMinuteSecond(seconds: number) {
let secs = Math.floor(seconds); let secs = Math.floor(seconds);
let secsLeftover = secs % 60; let secsLeftover = secs % 60;
let minutes = (secs - secsLeftover) / 60; let minutes = (secs - secsLeftover) / 60;
let secondsFormatted = secsLeftover < 10 ? `0${secsLeftover}` : `${secsLeftover}`; let secondsFormatted = secsLeftover < 10 ? `0${secsLeftover}` : `${secsLeftover}`;
let minutesFormatted = minutes < 10 ? `0${minutes}` : `${minutes}`; let minutesFormatted = minutes < 10 ? `0${minutes}` : `${minutes}`;
return `${minutesFormatted}:${secondsFormatted}`; return `${minutesFormatted}:${secondsFormatted}`;
} }
export function interceptKeys(extraActions: [string, () => void][] = []): (event: KeyboardEvent) => void { export function interceptKeys(
return (event) => { extraActions: [string, () => void][] = []
const tagName = document.activeElement?.tagName ?? ''; ): (event: KeyboardEvent) => void {
const audio = getAudioElement(); return (event) => {
const actions = new Map([ const tagName = document.activeElement?.tagName ?? '';
...extraActions, const audio = getAudioElement();
['Space', () => { const actions = new Map([
if (audio !== null) { ...extraActions,
audio.paused ? audio.play() : audio.pause(); [
} 'Space',
}], () => {
['KeyM', () => { if (audio !== null) {
if (audio !== null) { audio.paused ? audio.play() : audio.pause();
audio.muted = !audio.muted; }
} }
}], ],
[ [
'ArrowLeft', 'KeyM',
() => { () => {
if (audio !== null) { if (audio !== null) {
audio.currentTime -= 5; audio.muted = !audio.muted;
} }
} }
], ],
[ [
'ArrowRight', 'ArrowLeft',
() => { () => {
const audio = getAudioElement(); if (audio !== null) {
if (audio !== null) { audio.currentTime -= 5;
audio.currentTime += 5; }
} }
} ],
] [
]); 'ArrowRight',
if (tagName !== 'INPUT' && actions.has(event.code)) { () => {
event.preventDefault(); const audio = getAudioElement();
event.stopPropagation(); if (audio !== null) {
const action = actions.get(event.code) ?? null; audio.currentTime += 5;
if (action !== null) { }
action(); }
} ]
} ]);
} if (tagName !== 'INPUT' && actions.has(event.code)) {
} event.preventDefault();
event.stopPropagation();
const action = actions.get(event.code) ?? null;
if (action !== null) {
action();
}
}
};
}

View File

@ -1,17 +1,12 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
'./src/**/*.{html,js,svelte,ts}', './src/**/*.{html,js,svelte,ts}',
require('path').join(require.resolve( require('path').join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
'@skeletonlabs/skeleton'), ],
'../**/*.{html,js,svelte,ts}' theme: {
), extend: {}
], },
theme: { plugins: [...require('@skeletonlabs/skeleton/tailwind/skeleton.cjs')()],
extend: {}, darkMode: 'class'
}, };
plugins: [
...require('@skeletonlabs/skeleton/tailwind/skeleton.cjs')()
],
darkMode: 'class',
}

View File

@ -1,12 +1,12 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import Icons from 'unplugin-icons/vite' import Icons from 'unplugin-icons/vite';
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
sveltekit(), sveltekit(),
Icons({ Icons({
compiler: 'svelte', compiler: 'svelte'
}) })
] ]
}); });