diff --git a/biome.json b/biome.json
index 7ec4861..6127ee9 100644
--- a/biome.json
+++ b/biome.json
@@ -1,5 +1,5 @@
{
- "$schema": "https://biomejs.dev/schemas/2.5.2/schema.json",
+ "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
diff --git a/flake.nix b/flake.nix
index 2013855..a491696 100644
--- a/flake.nix
+++ b/flake.nix
@@ -19,7 +19,7 @@
version = "2.36.8";
src = ./.;
- npmDepsHash = "sha256-YkdBHUx6p61mMBSJCXNA3lbQ8Rd92F7CNJ7J+315Pug=";
+ npmDepsHash = "sha256-Loy++nRVvQBSmNyKJ4XPTVZdLwQdnKTb0uf4E3jQpiY=";
npmBuildScript = "build";
installPhase = ''
diff --git a/index.html b/index.html
index 8b1de42..3344c66 100644
--- a/index.html
+++ b/index.html
@@ -10,7 +10,7 @@
-
+
=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
+ "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-darwin-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
+ "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-freebsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
+ "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
+ "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
+ "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-loong64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
+ "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-mips64el": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
+ "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-ppc64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
+ "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-riscv64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
+ "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-s390x": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
+ "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-linux-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
+ "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-netbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
+ "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-openbsd-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
+ "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-sunos-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
+ "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-arm64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
+ "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/@typescript/typescript-win32-x64": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
+ "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/@vite-pwa/assets-generator": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@vite-pwa/assets-generator/-/assets-generator-1.0.2.tgz",
@@ -6010,9 +6350,9 @@
}
},
"node_modules/knip": {
- "version": "6.24.0",
- "resolved": "https://registry.npmjs.org/knip/-/knip-6.24.0.tgz",
- "integrity": "sha512-PokLlgeEjLh1rAsB7ts+52wZ37HBr1nDhE6NNONwEaXdeZGCJOkP7ZlIAI2Gtu8xohquzTWy75bc/1diI9shQw==",
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/knip/-/knip-6.26.0.tgz",
+ "integrity": "sha512-e9eELEEpBpGTd4H4HB7818/DYj9dMzMyUqAddfYwUN/EbSkgIjOuWEF96W/xHsmV0SDrsdXjIM+oZ2xpPzPsBA==",
"dev": true,
"funding": [
{
@@ -6363,9 +6703,9 @@
}
},
"node_modules/lucide-static": {
- "version": "1.23.0",
- "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-1.23.0.tgz",
- "integrity": "sha512-V4tqLEHfvsptGaAZMD8sFJKeGsz2nTAU0+ctxTX3afao/5bs+mhrvvGDmvQmyI7YhQwpIFodZmnkPTMYyGzH4Q==",
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-1.24.0.tgz",
+ "integrity": "sha512-NDSgPb/RWllI9QooPbGXfQCXQi/45oquDmtljeN3qVxqfEUf91uM2C4zijy8bs+pEWxKckA0/WZLZT43YD4Xnw==",
"dev": true,
"license": "ISC"
},
@@ -7587,9 +7927,9 @@
}
},
"node_modules/svelte-check": {
- "version": "4.7.1",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.1.tgz",
- "integrity": "sha512-FGUOmAqxXdN/H9Zm8slrqO7SLtFisXRB7rfOsHNJ3MLTD2po/+Stg8XyErkpumPHbuUiYTcqrEIzxpVWKTLqtg==",
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.2.tgz",
+ "integrity": "sha512-GoS4XJdGswlq0rIT1vtFLzJY1bvHtY37McY9H9Gkm1Ggw/ICdZYn8J/Z8Yi0BEL0i3R4+jtaWVePjyppMlij/A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7793,17 +8133,38 @@
}
},
"node_modules/typescript": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
- "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
+ "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
+ "tsc": "bin/tsc"
},
"engines": {
- "node": ">=14.17"
+ "node": ">=16.20.0"
+ },
+ "optionalDependencies": {
+ "@typescript/typescript-aix-ppc64": "7.0.2",
+ "@typescript/typescript-darwin-arm64": "7.0.2",
+ "@typescript/typescript-darwin-x64": "7.0.2",
+ "@typescript/typescript-freebsd-arm64": "7.0.2",
+ "@typescript/typescript-freebsd-x64": "7.0.2",
+ "@typescript/typescript-linux-arm": "7.0.2",
+ "@typescript/typescript-linux-arm64": "7.0.2",
+ "@typescript/typescript-linux-loong64": "7.0.2",
+ "@typescript/typescript-linux-mips64el": "7.0.2",
+ "@typescript/typescript-linux-ppc64": "7.0.2",
+ "@typescript/typescript-linux-riscv64": "7.0.2",
+ "@typescript/typescript-linux-s390x": "7.0.2",
+ "@typescript/typescript-linux-x64": "7.0.2",
+ "@typescript/typescript-netbsd-arm64": "7.0.2",
+ "@typescript/typescript-netbsd-x64": "7.0.2",
+ "@typescript/typescript-openbsd-arm64": "7.0.2",
+ "@typescript/typescript-openbsd-x64": "7.0.2",
+ "@typescript/typescript-sunos-x64": "7.0.2",
+ "@typescript/typescript-win32-arm64": "7.0.2",
+ "@typescript/typescript-win32-x64": "7.0.2"
}
},
"node_modules/unbash": {
@@ -7983,16 +8344,16 @@
}
},
"node_modules/vite": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
- "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
+ "version": "8.1.4",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz",
+ "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
- "picomatch": "^4.0.4",
+ "picomatch": "^4.0.5",
"postcss": "^8.5.16",
- "rolldown": "~1.1.3",
+ "rolldown": "~1.1.4",
"tinyglobby": "^0.2.17"
},
"bin": {
diff --git a/package.json b/package.json
index 0ddf91e..74dc1b9 100644
--- a/package.json
+++ b/package.json
@@ -3,8 +3,9 @@
"version": "2.36.8",
"type": "module",
"scripts": {
+ "generate:pwa-assets": "pwa-assets-generator",
"dev": "vite",
- "build": "vite build --mode production",
+ "build": "vite build",
"preview": "vite preview",
"check:svelte": "svelte-check --tsconfig ./tsconfig.app.json",
"check:tsc": "tsc --noEmit",
@@ -14,24 +15,24 @@
"fix": "biome check --write"
},
"devDependencies": {
- "@biomejs/biome": "2.5.2",
+ "@biomejs/biome": "2.5.3",
"@intergrav/famfamfam-silk-svg": "^1.0.0",
- "@sveltejs/vite-plugin-svelte": "^7.1.2",
+ "@sveltejs/vite-plugin-svelte": "^7.2.0",
"@tsconfig/svelte": "^5.0.8",
"@types/colorthief": "^2.6.1",
- "@types/node": "^26.1.0",
+ "@types/node": "^26.1.1",
"@types/spark-md5": "^3.0.5",
"@vite-pwa/assets-generator": "^1.0.2",
"colorthief": "^3.4.0",
"heroicons": "^2.2.0",
- "knip": "^6.24.0",
- "lucide-static": "^1.23.0",
+ "knip": "^6.26.0",
+ "lucide-static": "^1.24.0",
"modern-normalize": "^3.0.1",
"spark-md5": "^3.0.2",
"svelte": "^5.56.4",
- "svelte-check": "^4.7.1",
- "typescript": "~6.0.3",
- "vite": "^8.1.3",
+ "svelte-check": "^4.7.2",
+ "typescript": "~7.0.2",
+ "vite": "^8.1.4",
"vite-plugin-pwa": "^1.3.0",
"workbox-window": "^7.4.1"
}
diff --git a/src/app.css b/src/app.css
index 0f844c5..8118752 100644
--- a/src/app.css
+++ b/src/app.css
@@ -16,7 +16,6 @@
--radius-sm: 2px;
--radius-md: 4px;
- --radius-lg: 8px;
}
html {
diff --git a/src/lib/Library.svelte b/src/lib/Library.svelte
index 988fb7c..36c7c05 100644
--- a/src/lib/Library.svelte
+++ b/src/lib/Library.svelte
@@ -14,6 +14,17 @@ import TreeItem from "./TreeItem.svelte";
let query = $state(""),
results = $state | null>(null);
+const simpleAlbumTypes: AlbumListType[] = [
+ "random",
+ "newest",
+ "highest",
+ "frequent",
+ "recent",
+ "alphabeticalByName",
+ "alphabeticalByArtist",
+ "starred",
+];
+
let expanded = $state({
artists: true,
playlists: true,
@@ -39,17 +50,7 @@ function parseAlbumsPrefix(raw: string): {
if (!match) return null;
const typePart = match[1];
const arg = match[2]?.trim();
- const validSimple: AlbumListType[] = [
- "random",
- "newest",
- "highest",
- "frequent",
- "recent",
- "alphabeticalByName",
- "alphabeticalByArtist",
- "starred",
- ];
- const normalizedType = validSimple.find(
+ const normalizedType = simpleAlbumTypes.find(
(t) => t.toLowerCase() === typePart.toLowerCase(),
);
if (normalizedType) return { type: normalizedType };
diff --git a/src/lib/NowPlaying.svelte b/src/lib/NowPlaying.svelte
index 57c9435..a6d287d 100644
--- a/src/lib/NowPlaying.svelte
+++ b/src/lib/NowPlaying.svelte
@@ -39,8 +39,7 @@ let currentLine = $derived(lyrics.sync[currentLineIndex]?.text || "");
async function loadLyrics() {
const track = player.track;
- if (!track) return;
- if (isRadio(track)) {
+ if (!track || isRadio(track)) {
lyrics.full = "";
lyrics.sync = [];
return;
diff --git a/src/lib/app.svelte.ts b/src/lib/app.svelte.ts
index e3b0e63..43d6d05 100644
--- a/src/lib/app.svelte.ts
+++ b/src/lib/app.svelte.ts
@@ -7,6 +7,7 @@ export * from "./settings.svelte.js";
export * from "./ui.svelte.js";
import { restoreSession } from "./auth.svelte.js";
+import { ensureIconPack } from "./icons/packs.svelte.js";
import { rtlLangs } from "./languages/index.js";
import { ensureLocale } from "./languages/locales.svelte.js";
import { offlineManager } from "./offline/offlineManager.svelte.js";
@@ -116,6 +117,7 @@ $effect.root(() => {
$effect(() => {
if (!ui.busy) {
void ensureLocale(settings.lang);
+ void ensureIconPack(settings.iconPack);
document.documentElement.lang = settings.lang;
document.documentElement.dir = rtlLangs.has(settings.lang)
? "rtl"
diff --git a/src/lib/auth.svelte.ts b/src/lib/auth.svelte.ts
index de44d82..92ad362 100644
--- a/src/lib/auth.svelte.ts
+++ b/src/lib/auth.svelte.ts
@@ -14,6 +14,25 @@ import { syncQueue } from "./queue.svelte.js";
const isAdminRole = (role: unknown) => role === true || role === "true";
+const loadSessionData = async (username: string) => {
+ let admin = false;
+ const tasks: Promise[] = [loadLib(), syncQueue()];
+ if (username) {
+ tasks.push(
+ api
+ .getUser(username)
+ .then((user) => {
+ admin = isAdminRole(user.adminRole);
+ })
+ .catch((err) => {
+ console.error("failed to fetch user info:", err);
+ }),
+ );
+ }
+ await Promise.all(tasks);
+ return admin;
+};
+
export const auth = $state({
loggedIn: false,
busy: false,
@@ -78,23 +97,7 @@ export const login = async (
}
}
- let admin = false;
- const tasks: Promise[] = [loadLib(), syncQueue()];
-
- if (username) {
- tasks.push(
- api
- .getUser(username)
- .then((user) => {
- admin = isAdminRole(user.adminRole);
- })
- .catch((err) => {
- console.error("failed to fetch user info:", err);
- }),
- );
- }
-
- await Promise.all(tasks);
+ const admin = await loadSessionData(username);
persistCredentials();
offlineManager.setConnected();
@@ -128,27 +131,10 @@ export const logout = async () => {
export const isOwner = (owner?: string | null) =>
!owner || !auth.user || owner.toLowerCase() === auth.user.toLowerCase();
-const refreshSession = async () => {
- const tasks: Promise[] = [loadLib(), syncQueue()];
- if (auth.user) {
- tasks.push(
- api
- .getUser(auth.user)
- .then((user) => {
- auth.admin = isAdminRole(user.adminRole);
- })
- .catch((err) => {
- console.error("failed to fetch user info:", err);
- }),
- );
- }
- await Promise.all(tasks);
-};
-
export const reconnectSession = async () => {
try {
await detectExtensions();
- await refreshSession();
+ auth.admin = await loadSessionData(auth.user);
} catch (err) {
console.error("failed to reconnect session:", err);
}
diff --git a/src/lib/client.svelte.ts b/src/lib/client.svelte.ts
index 1ee7080..eac7b9c 100644
--- a/src/lib/client.svelte.ts
+++ b/src/lib/client.svelte.ts
@@ -759,10 +759,14 @@ let sparkMd5Promise: Promise | null = null;
export const createToken = async (password: string) => {
const salt = Math.random().toString(36).substring(2, 8);
- if (!sparkMd5Promise)
- sparkMd5Promise = import("spark-md5").then(
- (mod) => ("default" in mod ? mod.default : mod) as SparkMd5Lib,
- );
+ if (!sparkMd5Promise) {
+ sparkMd5Promise = import("spark-md5")
+ .then((mod) => ("default" in mod ? mod.default : mod) as SparkMd5Lib)
+ .catch((err: unknown) => {
+ sparkMd5Promise = null;
+ throw err;
+ });
+ }
const SparkMD5 = await sparkMd5Promise;
return { token: SparkMD5.hash(password + salt), salt };
};
diff --git a/src/lib/dialogs/AuthModal.svelte b/src/lib/dialogs/AuthModal.svelte
index 1bb6c07..9f5be8c 100644
--- a/src/lib/dialogs/AuthModal.svelte
+++ b/src/lib/dialogs/AuthModal.svelte
@@ -63,7 +63,7 @@ $effect(() => {
console.warn("failed to fetch extensions anonymously:", err);
}
}
- } catch (err) {
+ } catch {
if (server !== requestServer) return;
pingStatus = "fail";
}
diff --git a/src/lib/dialogs/SettingsModal.svelte b/src/lib/dialogs/SettingsModal.svelte
index 89e5f5e..0d46552 100644
--- a/src/lib/dialogs/SettingsModal.svelte
+++ b/src/lib/dialogs/SettingsModal.svelte
@@ -1,6 +1,6 @@
{#if iconPack === "silk16" && iconPacks[iconPack]?.[name]}
diff --git a/src/lib/icons/index.ts b/src/lib/icons/index.ts
index 0f5bb1a..98923f7 100644
--- a/src/lib/icons/index.ts
+++ b/src/lib/icons/index.ts
@@ -25,14 +25,17 @@ const iconNames = [
export type IconName = (typeof iconNames)[number];
-export const iconPackNames = [
- "heroicons16solid",
- "heroicons20solid",
- "heroicons24solid",
- "heroicons24outline",
- "lucide24",
- "silk16",
+export const iconPacks = [
+ { id: "heroicons16solid", label: "heroicons (16 solid)", size: 16 },
+ { id: "heroicons20solid", label: "heroicons (20 solid)", size: 20 },
+ { id: "heroicons24solid", label: "heroicons (24 solid)", size: 24 },
+ { id: "heroicons24outline", label: "heroicons (24 outline)", size: 24 },
+ { id: "lucide24", label: "lucide (24)", size: 24 },
+ { id: "silk16", label: "silk (16)", size: 16 },
] as const;
-export type IconPackName = (typeof iconPackNames)[number];
+export type IconPackName = (typeof iconPacks)[number]["id"];
+export const iconPackNames: readonly IconPackName[] = iconPacks.map(
+ (pack) => pack.id,
+);
export type IconPack = Record;
diff --git a/src/lib/icons/packs.svelte.ts b/src/lib/icons/packs.svelte.ts
index 0bd33d7..2884e57 100644
--- a/src/lib/icons/packs.svelte.ts
+++ b/src/lib/icons/packs.svelte.ts
@@ -20,7 +20,12 @@ export async function ensureIconPack(name: IconPackName): Promise {
const load = iconPackLoaders[`./packs/${name}.ts`];
if (!load) return heroicons16solid;
- const { default: iconPack } = await load();
- iconPacks[name] = iconPack;
- return iconPack;
+ try {
+ const { default: iconPack } = await load();
+ iconPacks[name] = iconPack;
+ return iconPack;
+ } catch (err) {
+ console.error("failed to load icon pack:", name, err);
+ return heroicons16solid;
+ }
}
diff --git a/src/lib/languages/locales.svelte.ts b/src/lib/languages/locales.svelte.ts
index 7a048e3..afb409c 100644
--- a/src/lib/languages/locales.svelte.ts
+++ b/src/lib/languages/locales.svelte.ts
@@ -20,9 +20,14 @@ export async function ensureLocale(lang: LangCode): Promise {
const load = localeLoaders[`./locales/${lang}.ts`];
if (!load) return en;
- const { default: locale } = await load();
- loadedLocales[lang] = locale;
- return locale;
+ try {
+ const { default: locale } = await load();
+ loadedLocales[lang] = locale;
+ return locale;
+ } catch (err) {
+ console.error("failed to load locale:", lang, err);
+ return en;
+ }
}
export type { BaseDict, LangCode };
diff --git a/src/lib/library.svelte.ts b/src/lib/library.svelte.ts
index ebe19fc..f4ec4a3 100644
--- a/src/lib/library.svelte.ts
+++ b/src/lib/library.svelte.ts
@@ -32,6 +32,14 @@ export const loadPlaylists = async () => {
export const loadShares = async () => {
lib.busy = true;
+ try {
+ await refreshShares();
+ } finally {
+ lib.busy = false;
+ }
+};
+
+const refreshShares = async () => {
try {
lib.shares = await api.getShares();
lib.isSharingSupported = true;
@@ -39,8 +47,6 @@ export const loadShares = async () => {
console.warn("sharing not supported by server:", err);
lib.shares = [];
lib.isSharingSupported = false;
- } finally {
- lib.busy = false;
}
};
@@ -71,14 +77,7 @@ export const loadLib = async () => {
lib.playlists = playlists;
if (!offlineManager.isOfflineMode) {
- try {
- lib.shares = await api.getShares();
- lib.isSharingSupported = true;
- } catch (err) {
- console.warn("sharing not supported by server:", err);
- lib.shares = [];
- lib.isSharingSupported = false;
- }
+ await refreshShares();
await loadRadioStations();
} else {
diff --git a/src/lib/theme.svelte.ts b/src/lib/theme.svelte.ts
index 6029b01..6135002 100644
--- a/src/lib/theme.svelte.ts
+++ b/src/lib/theme.svelte.ts
@@ -11,8 +11,6 @@ interface DominantColor {
const cache = new Map();
-const dynamicColor = $state({ value: "" });
-
let colorThiefPromise: Promise | null = null;
const darkMQ = window.matchMedia("(prefers-color-scheme: dark)");
let themeMeta: HTMLMetaElement | null = null;
@@ -46,8 +44,6 @@ const applyTheme = (bg: string, text: string, dark: boolean) => {
style.setProperty("--text", text);
style.colorScheme = dark ? "dark" : "light";
updateMeta(bg);
-
- dynamicColor.value = bg;
};
const clearTheme = () => {
@@ -56,8 +52,6 @@ const clearTheme = () => {
style.removeProperty("--text");
style.colorScheme = "";
updateMeta();
-
- dynamicColor.value = "";
};
export const initTheme = () => {
@@ -85,7 +79,12 @@ export const initTheme = () => {
img.onerror = null;
if (!settings.dynamicColors || player.track?.id !== track.id) return;
try {
- if (!colorThiefPromise) colorThiefPromise = import("colorthief");
+ if (!colorThiefPromise) {
+ colorThiefPromise = import("colorthief").catch((err: unknown) => {
+ colorThiefPromise = null;
+ throw err;
+ });
+ }
const { getColor } = await colorThiefPromise;
const dominant = (await getColor(img, {
ignoreWhite: false,
diff --git a/src/main.ts b/src/main.ts
index 4b48aee..6ae4b59 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,6 +3,9 @@ import "./app.css";
import { mount } from "svelte";
import App from "./App.svelte";
+const target = document.getElementById("app");
+if (!target) throw new Error("Missing #app mount point");
+
export default mount(App, {
- target: document.getElementById("app") ?? document.body,
+ target,
});
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 6a12664..d4c5b2b 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -20,5 +20,5 @@
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
- "include": ["vite.config.ts"]
+ "include": ["vite.config.ts", "pwa-assets.config.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 6650802..ea7b825 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -15,10 +15,7 @@ export default defineConfig({
description:
"a simple but full-featured web player for OpenSubsonic compatible music servers",
display: "standalone",
- background_color: undefined,
theme_color: "#000000",
- lang: undefined,
- scope: undefined,
icons: [
{
src: "pwa-64x64.png",