Compare commits
10 commits
22f268e686
...
5e6541dce4
Author | SHA1 | Date | |
---|---|---|---|
5e6541dce4 | |||
074e7afa52 | |||
2163fe787f | |||
718a789e3c | |||
f3a49f2dc5 | |||
72a045d4bc | |||
251181843f | |||
97efbcec1b | |||
b5c2694bc0 | |||
9224aee6f8 |
64 changed files with 1449 additions and 884 deletions
|
@ -6,6 +6,7 @@ node_modules
|
|||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/scripts
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: svelte-lint
|
||||
name: svelte-lint
|
||||
- id: svelte-check
|
||||
name: svelte-check
|
||||
language: system
|
||||
pass_filenames: false
|
||||
entry: npm run lint
|
||||
entry: npm run check
|
||||
files: \.(js|ts|svelte)$
|
||||
- id: svelte-prettier
|
||||
name: prettier
|
||||
language: system
|
||||
entry: npx prettier --write --ignore-unknown
|
||||
types: [text]
|
||||
- id: svelte-lint
|
||||
name: eslint
|
||||
language: system
|
||||
entry: npx eslint
|
||||
files: \.(js|ts|svelte)$
|
||||
|
|
|
@ -7,7 +7,7 @@ node_modules
|
|||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/src/paraglide
|
||||
/src/i18n
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
|
|
5
.typesafe-i18n.json
Normal file
5
.typesafe-i18n.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json",
|
||||
"adapter": "svelte",
|
||||
"baseLocale": "en"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
# TIRAYA frontend
|
||||
# <img src="https://raw.githubusercontent.com/TirayaMusic/meta/main/dist/header.svg" alt="TIRAYA" height="100"> frontend
|
||||
|
||||
Frontend for the TIRAYA music streaming service.
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||
"hello_world": "Hallo Welt",
|
||||
"search": "Suche",
|
||||
"home": "Startseite",
|
||||
|
@ -29,11 +28,11 @@
|
|||
"unmute": "Stummschaltung aufheben",
|
||||
"sort_date": "Nach Datum sortieren",
|
||||
"sort_name": "Alphabetisch sortieren",
|
||||
"play_item": "{title} abspielen",
|
||||
"play_item": "{0} abspielen",
|
||||
"library_add": "Zur Sammlung hinzufügen",
|
||||
"library_remove": "Von der Sammlung entfernen",
|
||||
"search_no_result": "Es gibt keine {itemType}, die deiner Suche entsprechen",
|
||||
"empty_list": "Diese Liste enthält keine {itemType}",
|
||||
"search_no_result": "Es gibt keine {0|{artist: Künstler, album: Alben, track: Titel, playlist: Playlists, *: Elemente}}, die deiner Suche entsprechen",
|
||||
"empty_list": "Diese Liste enthält keine {0|{artist: Künstler, album: Alben, track: Titel, playlist: Playlists, *: Elemente}}",
|
||||
"clear_search": "Suche löschen",
|
||||
"title": "Titel",
|
||||
"album": "Album",
|
||||
|
@ -42,8 +41,8 @@
|
|||
"added_by": "Hinzugefügt von",
|
||||
"duration": "Dauer",
|
||||
"singles": "Singles",
|
||||
"search_item": "{item} durchsuchen",
|
||||
"top_tracks_of": "Beliebte Titel von {name}",
|
||||
"search_item": "{0} durchsuchen",
|
||||
"top_tracks_of": "Beliebte Titel von {0}",
|
||||
"all_tracks": "Alle Titel",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"playback_history": "Wiedergabeverlauf",
|
||||
|
@ -60,7 +59,7 @@
|
|||
"copy_url": "URL kopieren",
|
||||
"download_audio_files": "Audiodateien herunterladen",
|
||||
"search_create_playlist": "Playlist suchen/erstellen",
|
||||
"playlist_new_name": "Playlist \"{name}\" erstellen",
|
||||
"playlist_new_name": "Playlist \"{0}\" erstellen",
|
||||
"view_artist": "Zeige Künstler",
|
||||
"view_album": "Zeige Album",
|
||||
"view_track_info": "Zeige Titelinfo",
|
||||
|
@ -68,5 +67,13 @@
|
|||
"playlist_remove": "Von Playlist entfernen",
|
||||
"download_audio_file": "Audiodatei herunterladen",
|
||||
"copy_urls": "URLs kopieren",
|
||||
"language": "Sprache"
|
||||
"language": "Sprache",
|
||||
"login": "Anmeldung",
|
||||
"settings": "Einstellungen",
|
||||
"documentation": "Dokumentation",
|
||||
"frontend": "Frontend",
|
||||
"n_artists": "{0} Künstler",
|
||||
"n_albums": "{0} {{Album|Alben}}",
|
||||
"n_tracks": "{0} Titel",
|
||||
"n_playlists": "{0} Playlist{{|s}}"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||
"hello_world": "Hello World",
|
||||
"search": "Search",
|
||||
"home": "Home",
|
||||
|
@ -29,11 +28,11 @@
|
|||
"unmute": "Unmute",
|
||||
"sort_date": "Sort by date",
|
||||
"sort_name": "Sort by name",
|
||||
"play_item": "Play {title}",
|
||||
"play_item": "Play {0}",
|
||||
"library_add": "Add to library",
|
||||
"library_remove": "Remove from library",
|
||||
"search_no_result": "There are no {itemType} matching your search",
|
||||
"empty_list": "There are no {itemType} in this list",
|
||||
"search_no_result": "There are no {0|{artist: artists, album: albums, track: tracks, playlist: playlists, *: items}} matching your search",
|
||||
"empty_list": "There are no {0|{artist: artists, album: albums, track: tracks, playlist: playlists, *: items}} in this list",
|
||||
"clear_search": "Clear search",
|
||||
"title": "Title",
|
||||
"album": "Album",
|
||||
|
@ -42,8 +41,8 @@
|
|||
"added_by": "Added by",
|
||||
"duration": "Duration",
|
||||
"singles": "Singles",
|
||||
"search_item": "Search {item}",
|
||||
"top_tracks_of": "Top tracks of {name}",
|
||||
"search_item": "Search {0}",
|
||||
"top_tracks_of": "Top tracks of {0}",
|
||||
"all_tracks": "All tracks",
|
||||
"notifications": "Notifications",
|
||||
"playback_history": "Playback history",
|
||||
|
@ -60,7 +59,7 @@
|
|||
"copy_url": "Copy URL",
|
||||
"download_audio_files": "Download audio files",
|
||||
"search_create_playlist": "Search/create playlist",
|
||||
"playlist_new_name": "Create playlist \"{name}\"",
|
||||
"playlist_new_name": "Create playlist \"{0}\"",
|
||||
"view_artist": "View artist",
|
||||
"view_album": "View album",
|
||||
"view_track_info": "View track info",
|
||||
|
@ -68,5 +67,13 @@
|
|||
"playlist_remove": "Remove from playlist",
|
||||
"download_audio_file": "Download audio file",
|
||||
"copy_urls": "Copy URLs",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"login": "Login",
|
||||
"settings": "Settings",
|
||||
"documentation": "Documentation",
|
||||
"frontend": "Frontend",
|
||||
"n_artists": "{0} artist{{|s}}",
|
||||
"n_albums": "{0} album{{|s}}",
|
||||
"n_tracks": "{0} track{{|s}}",
|
||||
"n_playlists": "{0} playlist{{|s}}"
|
||||
}
|
||||
|
|
24
package.json
24
package.json
|
@ -5,32 +5,33 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "paraglide-js compile --project ./project.inlang && vite build",
|
||||
"build": "npm run typesafe-i18n && vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "vitest",
|
||||
"lint": "prettier --plugin prettier-plugin-svelte --check . && eslint . && npm run check",
|
||||
"format": "prettier --plugin prettier-plugin-svelte --write .",
|
||||
"postinstall": "paraglide-js compile --project ./project.inlang"
|
||||
"typesafe-i18n": "tsx scripts/import_translations.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.0.16",
|
||||
"@mdi/js": "^7.3.67",
|
||||
"@resreq/event-hub": "^1.6.0",
|
||||
"@tirayamusic/melt-ui": "^0.37.5",
|
||||
"daisyui": "^3.9.4",
|
||||
"daisyui": "^4.4.20",
|
||||
"fast-average-color": "^9.4.0",
|
||||
"svelte-inview": "^4.0.1",
|
||||
"svelte-local-storage-store": "^0.5.0"
|
||||
"svelte-local-storage-store": "^0.5.0",
|
||||
"typesafe-i18n": "^5.26.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inlang/paraglide-js": "1.0.0-prerelease.19",
|
||||
"@inlang/paraglide-js-adapter-vite": "^1.0.2",
|
||||
"@melt-ui/pp": "^0.1.4",
|
||||
"@sveltejs/adapter-static": "^2.0.3",
|
||||
"@sveltejs/kit": "^1.27.7",
|
||||
"@sveltejs/adapter-node": "^2.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@types/node": "^20.10.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.13.2",
|
||||
"@typescript-eslint/parser": "^6.13.2",
|
||||
"autoprefixer": "^10.4.16",
|
||||
|
@ -47,9 +48,10 @@
|
|||
"svelte-sequential-preprocessor": "^2.0.1",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^4.5.1",
|
||||
"vite-bundle-visualizer": "^0.10.1",
|
||||
"vitest": "^0.33.0"
|
||||
"vite": "^5.0.0",
|
||||
"vite-bundle-visualizer": "^1.0.0",
|
||||
"vitest": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
1348
pnpm-lock.yaml
1348
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"$schema": "https://inlang.com/schema/project-settings",
|
||||
"sourceLanguageTag": "en",
|
||||
"languageTags": ["en", "de"],
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
|
||||
],
|
||||
"plugin.inlang.messageFormat": {
|
||||
"pathPattern": "./messages/{languageTag}.json"
|
||||
}
|
||||
}
|
32
scripts/import_translations.ts
Normal file
32
scripts/import_translations.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Import translations from the /messages folder and
|
||||
* convert them to typesafe-i18n files.
|
||||
*/
|
||||
import type { BaseTranslation } from "typesafe-i18n";
|
||||
import {
|
||||
storeTranslationToDisk,
|
||||
type ImportLocaleMapping,
|
||||
} from "typesafe-i18n/importer";
|
||||
import type { Locales } from "../src/i18n/i18n-types";
|
||||
import fs from "fs";
|
||||
|
||||
const importTranslationFile = async (path: string, locale: string) => {
|
||||
const data = fs.readFileSync(path, "utf-8");
|
||||
const translation = JSON.parse(data);
|
||||
|
||||
const localeMapping: ImportLocaleMapping = {
|
||||
locale,
|
||||
translations: translation,
|
||||
};
|
||||
|
||||
const result = await storeTranslationToDisk(localeMapping);
|
||||
|
||||
console.log(`translations imported for locale '${result}'`);
|
||||
};
|
||||
|
||||
fs.readdirSync("messages").forEach((file) => {
|
||||
if (file.endsWith(".json")) {
|
||||
const locale = file.substring(0, file.length - 5);
|
||||
importTranslationFile(`messages/${file}`, locale);
|
||||
}
|
||||
});
|
6
src/app.d.ts
vendored
6
src/app.d.ts
vendored
|
@ -2,12 +2,16 @@
|
|||
|
||||
import type { Coords, Direction } from "$lib/util/types";
|
||||
import type { MeltEventHandler } from "@tirayamusic/melt-ui/internal/types";
|
||||
import type { Locales, TranslationFunctions } from "$i18n/i18n-types";
|
||||
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
interface Locals {
|
||||
locale: Locales;
|
||||
LL: TranslationFunctions;
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<!doctype html>
|
||||
<html lang="en" data-theme="tiraya">
|
||||
<html lang="%lang%" data-theme="tiraya">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.svg" />
|
||||
|
|
34
src/hooks.server.ts
Normal file
34
src/hooks.server.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import type { Locales } from "$i18n/i18n-types";
|
||||
import { detectLocale, i18n, locales } from "$i18n/i18n-util";
|
||||
import { loadAllLocales } from "$i18n/i18n-util.sync";
|
||||
import type { Handle, RequestEvent } from "@sveltejs/kit";
|
||||
import { initAcceptLanguageHeaderDetector } from "typesafe-i18n/detectors";
|
||||
|
||||
loadAllLocales();
|
||||
const L = i18n();
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const locale = getPreferredLocale(event);
|
||||
const LL = L[locale];
|
||||
|
||||
// bind locale and translation functions to current request
|
||||
event.locals.locale = locale;
|
||||
event.locals.LL = LL;
|
||||
|
||||
// replace html lang attribute with correct language
|
||||
return resolve(event, {
|
||||
transformPageChunk: ({ html }) => html.replace("%lang%", locale),
|
||||
});
|
||||
};
|
||||
|
||||
const getPreferredLocale = ({ request, cookies }: RequestEvent) => {
|
||||
const langCookie = cookies.get("lang");
|
||||
if (langCookie && locales.indexOf(langCookie as Locales) !== -1)
|
||||
return langCookie as Locales;
|
||||
|
||||
// detect the preferred language the user has configured in his browser
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language
|
||||
const acceptLanguageDetector = initAcceptLanguageHeaderDetector(request);
|
||||
|
||||
return detectLocale(acceptLanguageDetector);
|
||||
};
|
13
src/i18n/formatters.ts
Normal file
13
src/i18n/formatters.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import type { FormattersInitializer } from "typesafe-i18n";
|
||||
import type { Locales, Formatters } from "./i18n-types";
|
||||
import { date } from "typesafe-i18n/formatters";
|
||||
|
||||
export const initFormatters: FormattersInitializer<Locales, Formatters> = (
|
||||
locale: Locales
|
||||
) => {
|
||||
const formatters: Formatters = {
|
||||
simpleDate: date(locale, { day: "numeric", month: "short", year: "numeric" }),
|
||||
};
|
||||
|
||||
return formatters;
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import { melt } from "@tirayamusic/melt-ui";
|
||||
import type { MeltEvent } from "@tirayamusic/melt-ui/internal/types";
|
||||
|
@ -60,7 +60,7 @@
|
|||
<div class="item noicon">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={m.search_create_playlist()}
|
||||
placeholder={$LL.search_create_playlist()}
|
||||
on:keydown={onPlaylistSearchKeydown}
|
||||
bind:this={playlistSearchElm}
|
||||
bind:value={playlistSearch}
|
||||
|
@ -77,8 +77,8 @@
|
|||
<Icon path={mdiPlus} size={1.4} />
|
||||
<span
|
||||
>{playlistSearch
|
||||
? m.playlist_new_name({ name: playlistSearch })
|
||||
: m.playlist_new()}</span
|
||||
? $LL.playlist_new_name(playlistSearch)
|
||||
: $LL.playlist_new()}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Context menu for artists, albums and playlists -->
|
||||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import {
|
||||
mdiContentCopy,
|
||||
mdiDownload,
|
||||
|
@ -44,40 +44,40 @@
|
|||
<div class="ctxmenu" use:melt={menu}>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiPlaylistPlay} size={1.4} />
|
||||
<span>{m.play_next()}</span>
|
||||
<span>{$LL.play_next()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiPlaylistPlus} size={1.4} />
|
||||
<span>{m.add_to_queue()}</span>
|
||||
<span>{$LL.add_to_queue()}</span>
|
||||
</div>
|
||||
|
||||
<div class="item sep" use:melt={item}>
|
||||
<Icon path={mdiHeartOutline} size={1.4} />
|
||||
<span>{m.library_add()}</span>
|
||||
<span>{$LL.library_add()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={$playlistMenuTrigger}>
|
||||
<Icon path={mdiPlaylistMusic} size={1.4} />
|
||||
<span>{m.playlist_add()}</span>
|
||||
<span>{$LL.playlist_add()}</span>
|
||||
<div class="icon-r"><Icon path={mdiMenuRight} /></div>
|
||||
</div>
|
||||
|
||||
<div class="item sep" use:melt={item}>
|
||||
<Icon path={mdiPencil} size={1.4} />
|
||||
<span>{m.edit()}</span>
|
||||
<span>{$LL.edit()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiTrashCan} size={1.4} />
|
||||
<span>{m.trash()}</span>
|
||||
<span>{$LL.trash()}</span>
|
||||
</div>
|
||||
|
||||
<div class="item sep" use:melt={item}>
|
||||
<Icon path={mdiReload} size={1.4} />
|
||||
<span>{m.update_now()}</span>
|
||||
<span>{$LL.update_now()}</span>
|
||||
</div>
|
||||
|
||||
<div class="item sep" use:melt={$shareMenuTrigger}>
|
||||
<Icon path={mdiShare} size={1.4} />
|
||||
<span>{m.share()}</span>
|
||||
<span>{$LL.share()}</span>
|
||||
<div class="icon-r"><Icon path={mdiMenuRight} /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -87,11 +87,11 @@
|
|||
<div class="ctxmenu" use:melt={$shareMenu}>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiContentCopy} size={1.4} />
|
||||
<span>{m.copy_url()}</span>
|
||||
<span>{$LL.copy_url()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiDownload} size={1.4} />
|
||||
<span>{m.download_audio_files()}</span>
|
||||
<span>{$LL.download_audio_files()}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { mdiLogin, mdiCog, mdiBookOpen } from "@mdi/js";
|
||||
import { melt } from "@tirayamusic/melt-ui";
|
||||
|
||||
|
@ -17,19 +18,19 @@
|
|||
<div class="ctxmenu" use:melt={menu}>
|
||||
<div class="item" use:melt={item} on:m-click={() => alert("login")}>
|
||||
<Icon path={mdiLogin} size={1.4} />
|
||||
<span>Login</span>
|
||||
<span>{$LL.login()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiCog} size={1.4} />
|
||||
<span>Settings</span>
|
||||
<span>{$LL.settings()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiBookOpen} size={1.4} />
|
||||
<span>Documentation</span>
|
||||
<span>{$LL.documentation()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={iconTiraya} size={1.4} />
|
||||
<span>Tiraya 0.1.0 • FE: 0.1.0</span>
|
||||
<span class="text-xs">{$LL.app_name()} 0.1.0<br />{$LL.frontend()} 0.1.0</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Context menu for tracks -->
|
||||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import {
|
||||
mdiPlaylistPlay,
|
||||
mdiPlaylistPlus,
|
||||
|
@ -72,44 +72,44 @@
|
|||
<div class="ctxmenu" use:melt={menu}>
|
||||
{#if !single}
|
||||
<div class="item item-sm item-primary">
|
||||
<span>{tracks.length} tracks</span>
|
||||
<span>{$LL.n_tracks(tracks.length)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Queue -->
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiPlaylistPlay} size={1.4} />
|
||||
<span>{m.play_next()}</span>
|
||||
<span>{$LL.play_next()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiPlaylistPlus} size={1.4} />
|
||||
<span>{m.add_to_queue()}</span>
|
||||
<span>{$LL.add_to_queue()}</span>
|
||||
</div>
|
||||
|
||||
<!-- Track details -->
|
||||
{#if single}
|
||||
<div class="item sep" use:melt={item}>
|
||||
<Icon path={mdiAccountMusic} size={1.4} />
|
||||
<span>{m.view_artist()}</span>
|
||||
<span>{$LL.view_artist()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiAlbum} size={1.4} />
|
||||
<span>{m.view_album()}</span>
|
||||
<span>{$LL.view_album()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiText} size={1.4} />
|
||||
<span>{m.view_track_info()}</span>
|
||||
<span>{$LL.view_track_info()}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- User library -->
|
||||
<div class="item sep" use:melt={item}>
|
||||
<Icon path={mdiHeartOutline} size={1.4} />
|
||||
<span>{m.library_add()}</span>
|
||||
<span>{$LL.library_add()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={$playlistMenuTrigger}>
|
||||
<Icon path={mdiPlaylistMusic} size={1.4} />
|
||||
<span>{m.playlist_add()}</span>
|
||||
<span>{$LL.playlist_add()}</span>
|
||||
<div class="icon-r"><Icon path={mdiMenuRight} /></div>
|
||||
</div>
|
||||
|
||||
|
@ -117,11 +117,11 @@
|
|||
{#if playlistIndex !== undefined}
|
||||
<div class="item sep" use:melt={item}>
|
||||
<Icon path={mdiTrashCan} size={1.4} />
|
||||
<span>{m.playlist_remove()}</span>
|
||||
<span>{$LL.playlist_remove()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={$moveMenuTrigger}>
|
||||
<Icon path={mdiSwapVertical} size={1.4} />
|
||||
<span>{m.move()}</span>
|
||||
<span>{$LL.move()}</span>
|
||||
<div class="icon-r"><Icon path={mdiMenuRight} /></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -129,7 +129,7 @@
|
|||
<!-- Share -->
|
||||
<div class="item sep" use:melt={$shareMenuTrigger}>
|
||||
<Icon path={mdiShare} size={1.4} />
|
||||
<span>{m.share()}</span>
|
||||
<span>{$LL.share()}</span>
|
||||
<div class="icon-r"><Icon path={mdiMenuRight} /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -150,11 +150,11 @@
|
|||
<div class="ctxmenu" use:melt={$shareMenu}>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiContentCopy} size={1.4} />
|
||||
<span>{single ? m.copy_url() : m.copy_urls()}</span>
|
||||
<span>{single ? $LL.copy_url() : $LL.copy_urls()}</span>
|
||||
</div>
|
||||
<div class="item" use:melt={item}>
|
||||
<Icon path={mdiDownload} size={1.4} />
|
||||
<span>{single ? m.download_audio_file() : m.download_audio_files()}</span>
|
||||
<span>{single ? $LL.download_audio_file() : $LL.download_audio_files()}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Fixed header that is shown when scrolling down -->
|
||||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { beforeNavigate } from "$app/navigation";
|
||||
import { mdiBellOutline, mdiClose, mdiHistory, mdiPlay } from "@mdi/js";
|
||||
|
@ -29,7 +29,7 @@
|
|||
} else if (t) {
|
||||
title = t;
|
||||
} else {
|
||||
title = m.app_name();
|
||||
title = $LL.app_name();
|
||||
}
|
||||
}
|
||||
$: searchCtx = $page.data.header?.searchCtx;
|
||||
|
@ -55,11 +55,11 @@
|
|||
)
|
||||
: 1;
|
||||
|
||||
let bgcolor = "hsl(var(--b2)";
|
||||
let bgcolor = "oklch(var(--b2)";
|
||||
$: if ($headerColor) {
|
||||
bgcolor = `rgb(${$headerColor.r}, ${$headerColor.g}, ${$headerColor.b}, ${opacity})`;
|
||||
} else {
|
||||
bgcolor = `hsl(var(--b2) / ${opacity})`;
|
||||
bgcolor = `oklch(var(--b2) / ${opacity})`;
|
||||
}
|
||||
|
||||
let inputElm: HTMLInputElement;
|
||||
|
@ -68,8 +68,8 @@
|
|||
|
||||
let searchLabel: string;
|
||||
$: if (searchCtx) {
|
||||
if (searchCtx === SearchCtx.Global) searchLabel = m.search();
|
||||
else searchLabel = m.search_item({ item: title });
|
||||
if (searchCtx === SearchCtx.Global) searchLabel = $LL.search();
|
||||
else searchLabel = $LL.search_item(title);
|
||||
}
|
||||
|
||||
function searchOnBlur(e: FocusEvent) {
|
||||
|
@ -119,7 +119,7 @@
|
|||
{#if $page.data.header?.playLink}
|
||||
<IconButton
|
||||
path={mdiPlay}
|
||||
ariaLabel={m.play_item({ title })}
|
||||
ariaLabel={$LL.play_item(title)}
|
||||
color="primary"
|
||||
on:click={() => alert("Playing " + title)}
|
||||
/>
|
||||
|
@ -135,13 +135,13 @@
|
|||
|
||||
<div class="flex gap-1 items-center">
|
||||
{#if homeScreen}
|
||||
<IconButton path={mdiBellOutline} ariaLabel={m.notifications()} />
|
||||
<IconButton path={mdiHistory} ariaLabel={m.playback_history()} />
|
||||
<IconButton path={mdiBellOutline} ariaLabel={$LL.notifications()} />
|
||||
<IconButton path={mdiHistory} ariaLabel={$LL.playback_history()} />
|
||||
{/if}
|
||||
{#if searchCtx}
|
||||
<IconButton
|
||||
path={searchShow ? mdiClose : iconSearch}
|
||||
ariaLabel={searchShow ? m.clear_search() : searchLabel}
|
||||
ariaLabel={searchShow ? $LL.clear_search() : searchLabel}
|
||||
on:click={() => {
|
||||
if (searchShow) {
|
||||
clearSearch();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { melt, createDropdownMenu } from "@tirayamusic/melt-ui";
|
||||
|
||||
import { POSITIONING_DROPDOWN } from "$lib/util/constants";
|
||||
|
@ -14,7 +14,7 @@
|
|||
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-sm avatar"
|
||||
aria-label={m.options()}
|
||||
aria-label={$LL.options()}
|
||||
use:melt={$trigger}
|
||||
>
|
||||
<img
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
languageTag,
|
||||
onSetLanguageTag,
|
||||
type AvailableLanguageTag,
|
||||
setLanguageTag,
|
||||
} from "$paraglide/runtime";
|
||||
|
||||
// initialize the language tag
|
||||
let _languageTag: AvailableLanguageTag = languageTag();
|
||||
|
||||
onSetLanguageTag((newLanguageTag) => {
|
||||
_languageTag = newLanguageTag;
|
||||
document.documentElement.lang = newLanguageTag;
|
||||
});
|
||||
|
||||
setLanguageTag("de");
|
||||
</script>
|
||||
|
||||
{#key _languageTag}
|
||||
<slot />
|
||||
{/key}
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { createEventDispatcher, onDestroy, onMount } from "svelte";
|
||||
import { mdiClockOutline } from "@mdi/js";
|
||||
import { generateId, kbd } from "@tirayamusic/melt-ui/internal/helpers";
|
||||
|
@ -421,7 +421,7 @@
|
|||
frame = requestAnimationFrame(poll);
|
||||
});
|
||||
onDestroy(() => {
|
||||
cancelAnimationFrame(frame);
|
||||
if (frame) cancelAnimationFrame(frame);
|
||||
});
|
||||
|
||||
// DRAG AND DROP
|
||||
|
@ -691,35 +691,35 @@
|
|||
</div>
|
||||
<div class="col first" role="columnheader" aria-colindex={2}>
|
||||
<SortTitle bind:sorting col={Filters.TITLE} col2="artist">
|
||||
<span class="ellipsis-ol">{m.title()}</span>
|
||||
<span class="ellipsis-ol">{$LL.title()}</span>
|
||||
<span slot="t2" class="ellipsis-ol">Artist</span>
|
||||
</SortTitle>
|
||||
</div>
|
||||
{#if view > ListView.Album}
|
||||
<div class="col var1" role="columnheader" aria-colindex={3}>
|
||||
<SortTitle bind:sorting col={Filters.ALBUM}>
|
||||
<span class="ellipsis-ol">{m.album()}</span>
|
||||
<span class="ellipsis-ol">{$LL.album()}</span>
|
||||
</SortTitle>
|
||||
</div>
|
||||
{/if}
|
||||
{#if view > ListView.Album}
|
||||
<div class="col var2" role="columnheader" aria-colindex={4}>
|
||||
<SortTitle bind:sorting col={Filters.RELEASE_DATE}>
|
||||
<span class="ellipsis-ol">{m.release_date()}</span>
|
||||
<span class="ellipsis-ol">{$LL.release_date()}</span>
|
||||
</SortTitle>
|
||||
</div>
|
||||
{/if}
|
||||
{#if view === ListView.Playlist}
|
||||
<div class="col var3" role="columnheader" aria-colindex={5}>
|
||||
<SortTitle bind:sorting col={Filters.ADDED_DATE}>
|
||||
<span class="ellipsis-ol">{m.date_added()}</span>
|
||||
<span class="ellipsis-ol">{$LL.date_added()}</span>
|
||||
</SortTitle>
|
||||
</div>
|
||||
{/if}
|
||||
{#if showAuthors}
|
||||
<div class="col var4" role="columnheader" aria-colindex={6}>
|
||||
<SortTitle bind:sorting col={Filters.ADDED_BY}>
|
||||
<span class="ellipsis-ol">{m.added_by()}</span>
|
||||
<span class="ellipsis-ol">{$LL.added_by()}</span>
|
||||
</SortTitle>
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -729,7 +729,7 @@
|
|||
role="columnheader"
|
||||
aria-colindex={5}
|
||||
>
|
||||
<SortTitle bind:sorting col={Filters.DURATION} ariaLabel={m.duration()} end>
|
||||
<SortTitle bind:sorting col={Filters.DURATION} ariaLabel={$LL.duration()} end>
|
||||
<Icon path={mdiClockOutline} />
|
||||
</SortTitle>
|
||||
</div>
|
||||
|
@ -737,7 +737,7 @@
|
|||
</div>
|
||||
|
||||
{#if filteredTracks.length === 0}
|
||||
<NoItemsMsg itemType="tracks" search={Boolean(searchTerm)} />
|
||||
<NoItemsMsg itemType="track" search={Boolean(searchTerm)} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { locale } from "$i18n/i18n-svelte";
|
||||
import { mdiPlay, mdiDotsVertical } from "@mdi/js";
|
||||
import { createContextMenu, createDropdownMenu, melt } from "@tirayamusic/melt-ui";
|
||||
|
||||
|
@ -136,7 +137,7 @@
|
|||
<div class="col var2" role="gridcell" aria-colindex={4}>
|
||||
{#if track.item.album.releaseDate}
|
||||
<span class="ellipsis-ol">
|
||||
{formatDateStr(track.item.album.releaseDate)}
|
||||
{formatDateStr(track.item.album.releaseDate, $locale)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -146,7 +147,7 @@
|
|||
<div class="col var3" role="gridcell" aria-colindex={5}>
|
||||
{#if track.item.addedDate}
|
||||
<span class="ellipsis-ol">
|
||||
{formatDate(track.item.addedDate)}
|
||||
{formatDate(track.item.addedDate, $locale)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
@apply bg-base-100;
|
||||
|
||||
.row {
|
||||
border-bottom: 1px solid hsl(var(--bc) / 0.4);
|
||||
border-bottom: 1px solid oklch(var(--bc) / 0.4);
|
||||
height: 100%;
|
||||
|
||||
@apply font-semibold;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { page } from "$app/stores";
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import {
|
||||
mdiAccountMusic,
|
||||
mdiAccountMusicOutline,
|
||||
|
@ -25,31 +25,31 @@
|
|||
<div class="flex flex-col h-full">
|
||||
<ul class="menu menu-compact flex flex-col p-1">
|
||||
<NavbarItemLarge
|
||||
title={m.home()}
|
||||
title={$LL.home()}
|
||||
icon={mdiHomeOutline}
|
||||
iconActive={mdiHome}
|
||||
href="/"
|
||||
/>
|
||||
<NavbarItemLarge
|
||||
title={m.search()}
|
||||
title={$LL.search()}
|
||||
icon={iconSearch}
|
||||
iconActive={iconSearchFilled}
|
||||
href="/search"
|
||||
/>
|
||||
<NavbarItemLarge
|
||||
title={m.artists()}
|
||||
title={$LL.artists()}
|
||||
icon={mdiAccountMusicOutline}
|
||||
iconActive={mdiAccountMusic}
|
||||
href="/artist"
|
||||
/>
|
||||
<NavbarItemLarge
|
||||
title={m.albums()}
|
||||
title={$LL.albums()}
|
||||
icon={iconAlbumOutline}
|
||||
iconActive={mdiAlbum}
|
||||
href="/album"
|
||||
/>
|
||||
<NavbarItemLarge
|
||||
title={m.tracks()}
|
||||
title={$LL.tracks()}
|
||||
icon={mdiMusicNoteOutline}
|
||||
iconActive={mdiMusicNote}
|
||||
href="/tracks"
|
||||
|
@ -60,9 +60,9 @@
|
|||
<a
|
||||
href="/playlist"
|
||||
class="flex-1 text-sm text-base-content text-opacity-40 font-semibold"
|
||||
>{m.playlists()}</a
|
||||
>{$LL.playlists()}</a
|
||||
>
|
||||
<IconButton path={mdiPlus} size="xs" ariaLabel={m.playlist_new()} />
|
||||
<IconButton path={mdiPlus} size="xs" ariaLabel={$LL.playlist_new()} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 min-h-0 mb-2">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import {
|
||||
mdiAccountMusic,
|
||||
mdiAccountMusicOutline,
|
||||
|
@ -16,27 +16,32 @@
|
|||
</script>
|
||||
|
||||
<div class="btm-nav btm-nav-sm bg-base-300 z-50">
|
||||
<NavbarMobileItem title="Home" icon={mdiHomeOutline} iconActive={mdiHome} href="/" />
|
||||
<NavbarMobileItem
|
||||
title={m.search()}
|
||||
title={$LL.home()}
|
||||
icon={mdiHomeOutline}
|
||||
iconActive={mdiHome}
|
||||
href="/"
|
||||
/>
|
||||
<NavbarMobileItem
|
||||
title={$LL.search()}
|
||||
icon={iconSearch}
|
||||
iconActive={iconSearchFilled}
|
||||
href="/search"
|
||||
/>
|
||||
<NavbarMobileItem
|
||||
title={m.artists()}
|
||||
title={$LL.artists()}
|
||||
icon={mdiAccountMusicOutline}
|
||||
iconActive={mdiAccountMusic}
|
||||
href="/artist"
|
||||
/>
|
||||
<NavbarMobileItem
|
||||
title={m.albums()}
|
||||
title={$LL.albums()}
|
||||
icon={iconAlbumOutline}
|
||||
iconActive={mdiAlbum}
|
||||
href="/album"
|
||||
/>
|
||||
<NavbarMobileItem
|
||||
title={m.playlists()}
|
||||
title={$LL.playlists()}
|
||||
icon={mdiPlaylistMusicOutline}
|
||||
iconActive={mdiPlaylistMusic}
|
||||
href="/playlist"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { mdiChevronDown, mdiChevronUp } from "@mdi/js";
|
||||
|
||||
import IconButton from "$lib/components/ui/IconButton.svelte";
|
||||
|
@ -33,7 +33,7 @@
|
|||
size={isLarge ? "sm" : "xs"}
|
||||
color="default"
|
||||
cls="absolute top-0 right-0 opacity-0 hover:opacity-100 transition-opacity"
|
||||
ariaLabel={isLarge ? m.cover_show_small() : m.m_cover_show_large()}
|
||||
ariaLabel={isLarge ? $LL.cover_show_small() : $LL.m_cover_show_large()}
|
||||
on:click={toggleCurrentCoverLarge}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import {
|
||||
mdiPlay,
|
||||
mdiSkipPrevious,
|
||||
|
@ -48,7 +48,10 @@
|
|||
<div class="track">
|
||||
<div
|
||||
class="flex items-center justify-start"
|
||||
aria-label={m.now_playing({ title: "Across the Sea", artist: "Leaves' Eyes" })}
|
||||
aria-label={$LL.now_playing({
|
||||
title: "Across the Sea",
|
||||
artist: "Leaves' Eyes",
|
||||
})}
|
||||
>
|
||||
<CurrentCover />
|
||||
<TrackName {track} />
|
||||
|
@ -61,23 +64,23 @@
|
|||
<div class="flex w-full space-x-2 justify-center items-center">
|
||||
<IconToggle
|
||||
iconOn={mdiShuffle}
|
||||
ariaLabelOn={m.shuffle_enable()}
|
||||
ariaLabelOff={m.shuffle_disable()}
|
||||
ariaLabelOn={$LL.shuffle_enable()}
|
||||
ariaLabelOff={$LL.shuffle_disable()}
|
||||
/>
|
||||
<IconButton path={mdiSkipPrevious} size="md" ariaLabel={m.previous()} />
|
||||
<IconButton path={mdiSkipPrevious} size="md" ariaLabel={$LL.previous()} />
|
||||
<IconToggle
|
||||
iconOn={mdiPause}
|
||||
iconOff={mdiPlay}
|
||||
size="md"
|
||||
color="primary"
|
||||
iColorOn=""
|
||||
ariaLabelOn={m.play()}
|
||||
ariaLabelOff={m.pause()}
|
||||
ariaLabelOn={$LL.play()}
|
||||
ariaLabelOff={$LL.pause()}
|
||||
/>
|
||||
<IconButton path={mdiSkipNext} size="md" ariaLabel={m.next()} />
|
||||
<IconButton path={mdiSkipNext} size="md" ariaLabel={$LL.next()} />
|
||||
<IconToggleMulti
|
||||
icons={[mdiRepeat, mdiRepeat, mdiRepeatOnce]}
|
||||
ariaLabels={[m.repeat_disable(), m.repeat_queue(), m.repeat_track()]}
|
||||
ariaLabels={[$LL.repeat_disable(), $LL.repeat_queue(), $LL.repeat_track()]}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -91,7 +94,7 @@
|
|||
max={length}
|
||||
bind:value={position}
|
||||
bind:displayValue={displayPosition}
|
||||
ariaLabel={m.seek_bar()}
|
||||
ariaLabel={$LL.seek_bar()}
|
||||
/>
|
||||
<span class="w-12">{formatDuration(length)}</span>
|
||||
</div>
|
||||
|
@ -103,15 +106,15 @@
|
|||
<IconToggle
|
||||
iconOn={mdiPlaylistPlay}
|
||||
value={$clientState.showQueue}
|
||||
ariaLabelOn={m.queue_show()}
|
||||
ariaLabelOff={m.queue_hide()}
|
||||
ariaLabelOn={$LL.queue_show()}
|
||||
ariaLabelOff={$LL.queue_hide()}
|
||||
on:change={toggleShowQueue}
|
||||
/>
|
||||
<VolumeControl />
|
||||
<IconButton path={mdiFullscreen} ariaLabel={m.fullscreen_player()} />
|
||||
<IconButton path={mdiFullscreen} ariaLabel={$LL.fullscreen_player()} />
|
||||
<IconButtonTrigger
|
||||
path={mdiDotsVertical}
|
||||
ariaLabel={m.track_options()}
|
||||
ariaLabel={$LL.track_options()}
|
||||
trigger={$trigger}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import {
|
||||
mdiFullscreen,
|
||||
mdiPause,
|
||||
|
@ -75,10 +75,10 @@
|
|||
class="shadow rounded-md z-50"
|
||||
class:transition-colors={colorTrans}
|
||||
style={`background-color: ${bgColorHex}`}
|
||||
aria-label={m.now_playing({ title: "Across the Sea", artist: "Leaves' Eyes" })}
|
||||
aria-label={$LL.now_playing({ title: "Across the Sea", artist: "Leaves' Eyes" })}
|
||||
>
|
||||
<div class="inner">
|
||||
<button class="relative" aria-label={m.fullscreen_player()}>
|
||||
<button class="relative" aria-label={$LL.fullscreen_player()}>
|
||||
<div
|
||||
class="absolute w-full h-full flex items-center justify-center bg-base-100/50
|
||||
transition-opacity opacity-0 hover:opacity-100"
|
||||
|
@ -113,7 +113,7 @@
|
|||
<IconButton
|
||||
path={mdiSkipPrevious}
|
||||
size="md"
|
||||
ariaLabel={m.previous()}
|
||||
ariaLabel={$LL.previous()}
|
||||
on:click={prevTrack}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -122,14 +122,14 @@
|
|||
iconOff={mdiPlay}
|
||||
size="md"
|
||||
iColorOn=""
|
||||
ariaLabelOff={m.pause()}
|
||||
ariaLabelOn={m.play()}
|
||||
ariaLabelOff={$LL.pause()}
|
||||
ariaLabelOn={$LL.play()}
|
||||
/>
|
||||
{#if !$mainPhone}
|
||||
<IconButton
|
||||
path={mdiSkipNext}
|
||||
size="md"
|
||||
ariaLabel={m.next()}
|
||||
ariaLabel={$LL.next()}
|
||||
on:click={nextTrack}
|
||||
/>
|
||||
{/if}
|
||||
|
@ -137,7 +137,7 @@
|
|||
</div>
|
||||
|
||||
<div class="seekbar" bind:this={seekbarElm}>
|
||||
<Slider ariaLabel={m.seek_bar()} />
|
||||
<Slider ariaLabel={$LL.seek_bar()} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { mdiVolumeHigh, mdiVolumeMedium, mdiVolumeLow, mdiVolumeMute } from "@mdi/js";
|
||||
|
||||
import IconButton from "$lib/components/ui/IconButton.svelte";
|
||||
|
@ -40,14 +40,14 @@
|
|||
<div class="flex items-center" on:wheel|passive={onScroll}>
|
||||
<IconButton
|
||||
path={icon}
|
||||
ariaLabel={mute ? m.unmute() : m.mute()}
|
||||
ariaLabel={mute ? $LL.unmute() : $LL.mute()}
|
||||
on:click={toggleMute}
|
||||
/>
|
||||
<div class="inline-block w-20">
|
||||
<Slider
|
||||
min={0}
|
||||
max={100}
|
||||
ariaLabel={m.volume_control()}
|
||||
ariaLabel={$LL.volume_control()}
|
||||
value={mute ? 0 : volume}
|
||||
on:change={updateVolume}
|
||||
liveUpdate
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { mdiCalendar, mdiOrderAlphabeticalAscending } from "@mdi/js";
|
||||
|
||||
import {
|
||||
|
@ -94,21 +94,21 @@
|
|||
<div>
|
||||
<div class="flex flex-wrap gap-2 justify-between sticky-header py-2 z-20">
|
||||
<FilterButtons
|
||||
items={[`${m.albums()} (${nAlbums})`, `${m.singles()} (${nSingles})`]}
|
||||
items={[`${$LL.albums()} (${nAlbums})`, `${$LL.singles()} (${nSingles})`]}
|
||||
mask={[nAlbums === 0, nSingles === 0]}
|
||||
toggle
|
||||
bind:value={albumFilter}
|
||||
/>
|
||||
<FilterButtons
|
||||
items={[mdiCalendar, mdiOrderAlphabeticalAscending]}
|
||||
ariaLabels={[m.sort_date(), m.sort_name()]}
|
||||
ariaLabels={[$LL.sort_date(), $LL.sort_name()]}
|
||||
bind:value={albumSort}
|
||||
icons
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if searchTerm && filteredAlbums.length === 0}
|
||||
<EmptySearchNote itemType="albums" search={Boolean(searchTerm)} />
|
||||
<EmptySearchNote itemType="album" search={Boolean(searchTerm)} />
|
||||
{/if}
|
||||
|
||||
<Grid totalItems={filteredAlbums.length} bind:startIndex bind:endIndex>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
frame = requestAnimationFrame(poll);
|
||||
});
|
||||
onDestroy(() => {
|
||||
cancelAnimationFrame(frame);
|
||||
if (frame) cancelAnimationFrame(frame);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!-- Tile with a large image used to feature albums or playlists -->
|
||||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { mdiPlay } from "@mdi/js";
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
|||
<button
|
||||
class="btn btn-circle btn-primary absolute bottom-2 right-2 z-10"
|
||||
tabindex="-1"
|
||||
aria-label={m.play_item({ title })}
|
||||
aria-label={$LL.play_item(title)}
|
||||
on:click={(e) => {
|
||||
dispatch("play");
|
||||
e.preventDefault();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { mdiHeart, mdiHeartOutline } from "@mdi/js";
|
||||
|
||||
import IconToggle from "./IconToggle.svelte";
|
||||
|
@ -13,7 +13,7 @@
|
|||
iconOn={mdiHeart}
|
||||
iconOff={mdiHeartOutline}
|
||||
{size}
|
||||
ariaLabelOn={m.library_add()}
|
||||
ariaLabelOff={m.library_remove()}
|
||||
ariaLabelOn={$LL.library_add()}
|
||||
ariaLabelOff={$LL.library_remove()}
|
||||
bind:value
|
||||
/>
|
||||
|
|
105
src/lib/components/ui/LoadingAnimation.svelte
Normal file
105
src/lib/components/ui/LoadingAnimation.svelte
Normal file
|
@ -0,0 +1,105 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="100"
|
||||
height="100"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
xlink:href="#a"
|
||||
id="b"
|
||||
x1="75.381"
|
||||
x2="56.756"
|
||||
y1="72.556"
|
||||
y2="62.604"
|
||||
gradientTransform="rotate(120 159.185 84.508)scale(2.90865)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
/>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" style="stop-color:#16b5ab;stop-opacity:1" />
|
||||
<stop offset="1" style="stop-color:#00eaff;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#a"
|
||||
id="c"
|
||||
x1="75.381"
|
||||
x2="56.756"
|
||||
y1="72.556"
|
||||
y2="62.604"
|
||||
gradientTransform="rotate(-120 74.447 162.819)scale(2.90865)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
/>
|
||||
<linearGradient
|
||||
xlink:href="#a"
|
||||
id="d"
|
||||
x1="75.381"
|
||||
x2="56.756"
|
||||
y1="72.556"
|
||||
y2="62.604"
|
||||
gradientTransform="matrix(2.90865 0 0 2.90865 -135.58 -146.795)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
/>
|
||||
</defs>
|
||||
<path
|
||||
d="m 25.988281,17.615234 a 6.3502348,6.3502348 0 0 0 -9.52539,5.5 V 84.75 A 6.3495998,6.3495998 0 0 0 22.8125,91.099609 6.3495998,6.3495998 0 0 0 29.162109,84.75 V 34.111328 l 21.611328,12.478516 a 6.3495998,6.3495998 0 0 0 8.673829,-2.324219 6.3495998,6.3495998 0 0 0 -2.324219,-8.673828 z"
|
||||
class="border"
|
||||
/>
|
||||
<path
|
||||
d="m 30.591797,4.234375 a 6.3495998,6.3495998 0 0 0 -3.855469,2.9589844 6.3495998,6.3495998 0 0 0 2.324219,8.6718746 L 72.914062,41.185547 51.302734,53.662109 a 6.3495998,6.3495998 0 0 0 -2.324218,8.673828 6.3495998,6.3495998 0 0 0 8.673828,2.324219 L 88.787109,46.683594 a 6.3502348,6.3502348 0 0 0 0,-10.998047 L 35.410156,4.8691406 A 6.3495998,6.3495998 0 0 0 30.591797,4.234375 Z"
|
||||
class="border"
|
||||
/>
|
||||
<path
|
||||
d="m 38.623047,44.208984 a 6.3495998,6.3495998 0 0 0 -6.34961,6.34961 v 35.951172 a 6.3502348,6.3502348 0 0 0 9.523438,5.5 l 53.378906,-30.81836 a 6.3495998,6.3495998 0 0 0 2.322266,-8.673828 6.3495998,6.3495998 0 0 0 -8.671875,-2.324219 L 44.972656,75.511719 V 50.558594 a 6.3495998,6.3495998 0 0 0 -6.349609,-6.34961 z"
|
||||
class="border"
|
||||
/>
|
||||
|
||||
<path
|
||||
style="fill:none;stroke:url(#b);stroke-width:12.6992;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="m 22.813222,84.749657 5e-6,-61.634538 31.13496,17.975773"
|
||||
class="anim"
|
||||
/>
|
||||
<path
|
||||
style="fill:none;stroke:url(#c);stroke-width:12.6992;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="M 32.235584,10.367238 85.612628,41.184521 54.477692,59.160292"
|
||||
class="anim"
|
||||
/>
|
||||
<path
|
||||
style="fill:none;stroke:url(#d);stroke-width:12.6992;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||
d="M 92.000037,55.693052 38.622986,86.510304 v -35.95157"
|
||||
class="anim"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<style lang="postcss">
|
||||
/** Animation code from https://svgartista.net/ */
|
||||
@keyframes logo-loading {
|
||||
0% {
|
||||
stroke-dashoffset: 99.58604431152344px;
|
||||
stroke-dasharray: 99.58604431152344px;
|
||||
}
|
||||
|
||||
50% {
|
||||
stroke-dashoffset: 0;
|
||||
stroke-dasharray: 99.58604431152344px;
|
||||
}
|
||||
|
||||
50.001% {
|
||||
stroke-dashoffset: 199.17218017578125px;
|
||||
stroke-dasharray: 99.58604431152344px;
|
||||
}
|
||||
|
||||
100% {
|
||||
stroke-dashoffset: 99.58604431152344px;
|
||||
stroke-dasharray: 99.58604431152344px;
|
||||
}
|
||||
}
|
||||
|
||||
.border {
|
||||
stroke-width: 1;
|
||||
@apply stroke-base-content/30 fill-none;
|
||||
}
|
||||
|
||||
.anim {
|
||||
animation: logo-loading 2s ease 0s infinite;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -1,6 +1,6 @@
|
|||
<!-- Message shown to the user if a list or grid contains no items -->
|
||||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import { clearSearch } from "$lib/util/functions";
|
||||
import { randomKaomoji } from "$lib/util/kaomoji";
|
||||
|
||||
|
@ -13,11 +13,11 @@
|
|||
<p class="text-3xl text-base-content text-opacity-60 mb-2" aria-hidden="true">
|
||||
{randomKaomoji(404)}
|
||||
</p>
|
||||
<p>{m.search_no_result({ itemType })}</p>
|
||||
<p>{$LL.search_no_result(itemType)}</p>
|
||||
<button class="btn btn-outline btn-sm mt-2" on:click={clearSearch}
|
||||
>{m.clear_search()}</button
|
||||
>{$LL.clear_search()}</button
|
||||
>
|
||||
{:else}
|
||||
<p>{m.empty_list({ itemType })}</p>
|
||||
<p>{$LL.empty_list(itemType)}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { parseHslString } from "$lib/util/colors";
|
||||
import { parseOklchString } from "$lib/util/colors";
|
||||
import { WHITE } from "$lib/util/constants";
|
||||
import { getCssVariable } from "$lib/util/functions";
|
||||
import type { Color } from "$lib/util/types";
|
||||
|
@ -6,7 +6,7 @@ import { writable } from "svelte/store";
|
|||
|
||||
function getTextColor(): Color {
|
||||
const colorStr = getCssVariable("--bc");
|
||||
const parsed = parseHslString(colorStr);
|
||||
const parsed = parseOklchString(colorStr);
|
||||
return parsed || WHITE;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { correctBgColor } from "./colors";
|
||||
import { correctBgColor, oklchToRgb, colorToHex } from "./colors";
|
||||
import { WHITE } from "./constants";
|
||||
import type { Color } from "./types";
|
||||
|
||||
|
@ -10,3 +10,10 @@ describe("correctBgColor", () => {
|
|||
expect(correctBgColor(WHITE, YELLOW, 5)).toEqual({ r: 114, g: 114, b: 57 });
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
{ l: 0.7, c: 0.1, h: 141, rgb: "#7bae72" },
|
||||
{ l: 0.5294, c: 0.039, h: 230, rgb: "#54707e" },
|
||||
])("oklchToRgb", ({ l, c, h, rgb }) => {
|
||||
it("rgb", () => expect(colorToHex(oklchToRgb(l, c, h))).toBe(rgb));
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ export function colorToHex(c: Color): string {
|
|||
* @param l - Lightness percentage (0-100)
|
||||
* @returns color in RGB format
|
||||
*/
|
||||
function hslToRgb(h: number, s: number, l: number): Color {
|
||||
export function hslToRgb(h: number, s: number, l: number): Color {
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
const k = (n: number) => (n + h / 30) % 12;
|
||||
|
@ -29,6 +29,78 @@ function hslToRgb(h: number, s: number, l: number): Color {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a color from OKLCH to RGB
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch
|
||||
*
|
||||
* Conversion algorithm taken from https://github.com/Evercoder/culori
|
||||
* @author Dan Burzo
|
||||
* @license MIT
|
||||
*
|
||||
* @param l - Lightness (0-1)
|
||||
* @param c - Chroma (0-1)
|
||||
* @param h - Hue angle (0-1)
|
||||
* @returns color in RGB format
|
||||
*/
|
||||
export function oklchToRgb(l: number, c: number, h: number): Color {
|
||||
/* eslint-disable @typescript-eslint/no-loss-of-precision */
|
||||
|
||||
// Oklab
|
||||
const lch = {
|
||||
l,
|
||||
a: c ? c * Math.cos((h / 180) * Math.PI) : 0,
|
||||
b: c ? c * Math.sin((h / 180) * Math.PI) : 0,
|
||||
};
|
||||
|
||||
const L = Math.pow(
|
||||
l * 0.99999999845051981432 +
|
||||
0.39633779217376785678 * lch.a +
|
||||
0.21580375806075880339 * lch.b,
|
||||
3
|
||||
);
|
||||
const M = Math.pow(
|
||||
l * 1.0000000088817607767 -
|
||||
0.1055613423236563494 * lch.a -
|
||||
0.063854174771705903402 * lch.b,
|
||||
3
|
||||
);
|
||||
const S = Math.pow(
|
||||
l * 1.0000000546724109177 -
|
||||
0.089484182094965759684 * lch.a -
|
||||
1.2914855378640917399 * lch.b,
|
||||
3
|
||||
);
|
||||
const lrgb = {
|
||||
r: +4.076741661347994 * L - 3.307711590408193 * M + 0.230969928729428 * S,
|
||||
g: -1.2684380040921763 * L + 2.6097574006633715 * M - 0.3413193963102197 * S,
|
||||
b: -0.004196086541837188 * L - 0.7034186144594493 * M + 1.7076147009309444 * S,
|
||||
};
|
||||
|
||||
// RGB
|
||||
const rgbfn = (c: number) => {
|
||||
const abs = Math.abs(c);
|
||||
if (abs > 0.0031308) {
|
||||
return (Math.sign(c) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055) * 255;
|
||||
}
|
||||
return c * 12.92 * 255;
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/no-loss-of-precision */
|
||||
return {
|
||||
r: rgbfn(lrgb.r),
|
||||
g: rgbfn(lrgb.g),
|
||||
b: rgbfn(lrgb.b),
|
||||
};
|
||||
}
|
||||
|
||||
function parsePercentageString(s: string): number {
|
||||
if (s.endsWith("%")) {
|
||||
return parseFloat(s.substring(0, s.length - 1)) / 100;
|
||||
} else {
|
||||
return parseFloat(s);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string in HSL format (from CSS) and return its color.
|
||||
*
|
||||
|
@ -39,12 +111,24 @@ export function parseHslString(hsl: string): Color | null {
|
|||
if (parts.length !== 3) return null;
|
||||
|
||||
const h = parseInt(parts[0]);
|
||||
const s = parseInt(parts[1]);
|
||||
const l = parseInt(parts[2]);
|
||||
const s = parsePercentageString(parts[1]);
|
||||
const l = parsePercentageString(parts[2]);
|
||||
if (isNaN(h) || isNaN(s) || isNaN(l)) return null;
|
||||
return hslToRgb(h, s, l);
|
||||
}
|
||||
|
||||
export function parseOklchString(oklch: string): Color | null {
|
||||
const parts = oklch.split(" ", 3);
|
||||
if (parts.length !== 3) return null;
|
||||
|
||||
const l = parsePercentageString(parts[0]);
|
||||
const c = parseFloat(parts[1]);
|
||||
const h = parseFloat(parts[2]);
|
||||
|
||||
if (isNaN(l) || isNaN(c) || isNaN(h)) return null;
|
||||
return oklchToRgb(l, c, h);
|
||||
}
|
||||
|
||||
export function fastAverageColor(
|
||||
src: FastAverageColorResource | null,
|
||||
callback: (res: Color | null) => void
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { Color } from "./types";
|
|||
// Colors
|
||||
export const WHITE: Color = { r: 255, g: 255, b: 255 };
|
||||
export const MIN_CONTRAST_TITLE = 4.5;
|
||||
export const COLOR_B3 = "hsl(var(--b3))";
|
||||
export const COLOR_B3 = "oklch(var(--b3))";
|
||||
|
||||
// Layout
|
||||
export const MIN_CONTRAST_BODY = 7;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type { Page } from "@sveltejs/kit";
|
||||
import { DATE_LANG } from "./constants";
|
||||
import { CLEAR_SEARCH, hub } from "./events";
|
||||
import {
|
||||
LinkType,
|
||||
|
@ -57,24 +56,24 @@ export function dateToNumber(date: string | null | undefined): number | null {
|
|||
);
|
||||
}
|
||||
|
||||
export function formatDateStr(date: string): string | null {
|
||||
export function formatDateStr(date: string, locale: string): string | null {
|
||||
const parsed = parseDate(date);
|
||||
if (parsed === null) return null;
|
||||
if (parsed.month) {
|
||||
const dt = new Date(parsed.year, parsed.month, parsed.day || 1);
|
||||
return dt.toLocaleDateString(DATE_LANG, {
|
||||
day: parsed.day ? "numeric" : undefined,
|
||||
month: "short",
|
||||
return dt.toLocaleDateString(locale, {
|
||||
day: parsed.day ? "2-digit" : undefined,
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
return parsed.year.toString();
|
||||
}
|
||||
|
||||
export function formatDate(date: Date): string {
|
||||
return date.toLocaleDateString(DATE_LANG, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
export function formatDate(date: Date, locale: string): string {
|
||||
return date.toLocaleDateString(locale, {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
@ -249,3 +248,9 @@ export function arrMoveMulti<T>(arr: T[], positions: number[], target: number) {
|
|||
toInsert.reverse();
|
||||
arr.splice(target + offset, 0, ...toInsert);
|
||||
}
|
||||
|
||||
export function setCookie(cName: string, cValue: string, expDays: number) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + expDays * 24 * 60 * 60 * 1000);
|
||||
document.cookie = `${cName}=${cValue}; expires=${date.toUTCString()}; SameSite=Lax`;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const iconTiraya =
|
||||
"M7.966 1.982a1.317 1.33 0 0 0-.799.62 1.317 1.33 0 0 0 .483 1.816l9.623 5.612-4.79 2.794A1.317 1.33 0 0 0 12 14.639a1.317 1.33 0 0 0 1.8.488l6.764-3.946a1.317 1.33 0 0 0 0-2.303l-11.6-6.763a1.317 1.33 0 0 0-.998-.133Zm-2.364 2.93a1.317 1.33 0 0 0-.659 1.151V19.59a1.317 1.33 0 0 0 1.317 1.33 1.317 1.33 0 0 0 1.317-1.33V8.366l4.791 2.794a1.317 1.33 0 0 0 1.798-.486 1.317 1.33 0 0 0-.48-1.818L6.918 4.913a1.317 1.33 0 0 0-1.317 0zm4.094 5.845a1.317 1.33 0 0 0-1.317 1.33v7.89a1.317 1.33 0 0 0 1.976 1.15l11.599-6.762a1.317 1.33 0 0 0 .48-1.816 1.317 1.33 0 0 0-1.797-.487l-9.624 5.61v-5.584a1.317 1.33 0 0 0-1.317-1.33Z";
|
||||
"M9.27 10.61a1.524 1.524 0 0 0-1.525 1.524v8.628a1.524 1.524 0 0 0 2.286 1.32l12.81-7.396a1.524 1.524 0 0 0 .558-2.082 1.524 1.524 0 0 0-2.082-.557l-10.524 6.076v-5.989A1.524 1.524 0 0 0 9.27 10.61 M7.342 1.016a1.52 1.52 0 0 0-.926.71 1.524 1.524 0 0 0 .558 2.082l10.525 6.076-5.187 2.995a1.524 1.524 0 0 0-.558 2.081 1.524 1.524 0 0 0 2.082.558l7.472-4.314a1.524 1.524 0 0 0 0-2.64L8.498 1.169a1.52 1.52 0 0 0-1.156-.152 M4.713 4.228a1.52 1.52 0 0 0-.762 1.32v14.791a1.524 1.524 0 0 0 1.524 1.524A1.524 1.524 0 0 0 7 20.34V8.187l5.186 2.994a1.524 1.524 0 0 0 2.082-.558 1.524 1.524 0 0 0-.558-2.081L6.237 4.228a1.52 1.52 0 0 0-1.524 0";
|
||||
export const iconAlbumOutline =
|
||||
"m12 16.1q1.75 0 3-1.19t1.25-2.91q0-1.78-1.24-3.01t-3.01-1.24q-1.72 0-2.91 1.25t-1.19 3q0 1.72 1.19 2.91t2.91 1.19zm0-3.1q-0.425 0-0.712-0.288t-0.288-0.712 0.288-0.712 0.712-0.288 0.712 0.288 0.288 0.712-0.288 0.712-0.712 0.288zm0 9q-2.05 0-3.88-0.788t-3.19-2.15-2.15-3.19-0.788-3.88q0-2.08 0.788-3.9t2.15-3.18 3.19-2.14 3.88-0.788q2.08 0 3.9 0.788t3.18 2.14 2.14 3.18 0.788 3.9q0 2.05-0.788 3.88t-2.14 3.19-3.18 2.15-3.9 0.788zm0-1.5q3.55 0 6.02-2.49t2.48-6.01q0-3.55-2.48-6.02t-6.02-2.48q-3.52 0-6.01 2.48t-2.49 6.02q0 3.52 2.49 6.01t6.01 2.49z";
|
||||
export const iconSearch =
|
||||
|
|
6
src/routes/+layout.server.ts
Normal file
6
src/routes/+layout.server.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import type { LayoutServerLoad } from "./$types";
|
||||
|
||||
export const load: LayoutServerLoad = ({ locals: { locale } }) => {
|
||||
// pass locale information from "server-context" to "shared server + client context"
|
||||
return { locale };
|
||||
};
|
|
@ -5,6 +5,8 @@
|
|||
import { onMount } from "svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import { setLocale } from "$i18n/i18n-svelte";
|
||||
import type { LayoutData } from "./$types";
|
||||
|
||||
import {
|
||||
clientState,
|
||||
|
@ -31,7 +33,10 @@
|
|||
import NavbarMobile from "$lib/components/nav/NavbarMobile.svelte";
|
||||
import PlayerbarMobile from "$lib/components/player/PlayerbarMobile.svelte";
|
||||
import FixedHeader from "$lib/components/header/FixedHeader.svelte";
|
||||
import ParaglideJsProvider from "$lib/components/layout/ParaglideJsProvider.svelte";
|
||||
|
||||
export let data: LayoutData;
|
||||
// at the very top, set the locale before you access the store and before the actual rendering takes place
|
||||
setLocale(data.locale);
|
||||
|
||||
// Layout boundaries
|
||||
const NAVBAR_MIN = 150;
|
||||
|
@ -160,15 +165,14 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<ParaglideJsProvider>
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
id="app"
|
||||
class:mobile={!showNavbar}
|
||||
bind:clientWidth={$screenWidth}
|
||||
bind:clientHeight={$screenHeight}
|
||||
on:dragend={onDragEnd}
|
||||
>
|
||||
>
|
||||
<div
|
||||
id="main-content"
|
||||
style={`--navbar-width: ${navbarWidthSty}px; --queuebar-width: ${queuebarWidthSty}px;`}
|
||||
|
@ -222,8 +226,7 @@
|
|||
<PlayerbarMobile />
|
||||
<NavbarMobile />
|
||||
{/if}
|
||||
</div>
|
||||
</ParaglideJsProvider>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
#app {
|
||||
|
|
|
@ -1 +1,11 @@
|
|||
export const ssr = false;
|
||||
import type { LayoutLoad } from "./$types";
|
||||
import type { Locales } from "$i18n/i18n-types";
|
||||
import { loadLocaleAsync } from "$i18n/i18n-util.async";
|
||||
|
||||
export const load: LayoutLoad<{ locale: Locales }> = async ({ data: { locale } }) => {
|
||||
await loadLocaleAsync(locale);
|
||||
1;
|
||||
|
||||
// pass locale to the "rendering context"
|
||||
return { locale };
|
||||
};
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import {
|
||||
availableLanguageTags,
|
||||
languageTag,
|
||||
setLanguageTag,
|
||||
} from "$paraglide/runtime";
|
||||
import LL, { setLocale, locale } from "$i18n/i18n-svelte";
|
||||
import { locales } from "$i18n/i18n-util";
|
||||
import SimpleWrapper from "$lib/components/layout/SimpleWrapper.svelte";
|
||||
import DummyText from "$lib/components/ui/DummyText.svelte";
|
||||
import type { ChangeEventHandler } from "svelte/elements";
|
||||
import { loadLocaleAsync } from "$i18n/i18n-util.async";
|
||||
import type { Locales } from "$i18n/i18n-types";
|
||||
import { setCookie } from "$lib/util/functions";
|
||||
|
||||
const lf = (lang: string) =>
|
||||
`[${lang}] ${new Intl.DisplayNames(lang, { type: "language" }).of(lang)}`;
|
||||
|
||||
const langChange: ChangeEventHandler<HTMLSelectElement> = (e) => {
|
||||
setLanguageTag(availableLanguageTags[e.currentTarget.selectedIndex]);
|
||||
switchLocale(locales[e.currentTarget.selectedIndex]);
|
||||
};
|
||||
|
||||
const switchLocale = async (newLocale: Locales) => {
|
||||
await loadLocaleAsync(newLocale);
|
||||
setLocale(newLocale);
|
||||
setCookie("lang", newLocale, 365);
|
||||
};
|
||||
</script>
|
||||
|
||||
<SimpleWrapper>
|
||||
<h1 class="text-4xl">{m.hello_world()}</h1>
|
||||
<h1 class="text-4xl">{$LL.hello_world()}</h1>
|
||||
<div class="my-2">
|
||||
Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation
|
||||
</div>
|
||||
|
@ -34,11 +39,11 @@
|
|||
<div class="my-2">
|
||||
<label class="form-control">
|
||||
<div class="label">
|
||||
<span class="label-text">{m.language()}</span>
|
||||
<span class="label-text">{$LL.language()}</span>
|
||||
</div>
|
||||
<select class="select select-bordered w-full max-w-xs" on:change={langChange}>
|
||||
{#each availableLanguageTags as lang}
|
||||
<option selected={lang === languageTag()}>{lf(lang)}</option>
|
||||
{#each locales as lang}
|
||||
<option selected={lang === $locale}>{lf(lang)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import * as m from "$paraglide/messages";
|
||||
import type { HeaderData } from "$lib/util/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
import type { HeaderData } from "$lib/util/types";
|
||||
import { i18nObject } from "$i18n/i18n-util.js";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { locale } = await parent();
|
||||
const LL = i18nObject(locale);
|
||||
|
||||
export const load = (({ params }) => {
|
||||
return {
|
||||
header: { title: m.app_name } satisfies HeaderData,
|
||||
header: { title: LL.app_name() } satisfies HeaderData,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import * as m from "$paraglide/messages";
|
||||
import { SearchCtx, type HeaderData } from "$lib/util/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
import { i18nObject } from "$i18n/i18n-util";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { locale } = await parent();
|
||||
const LL = i18nObject(locale);
|
||||
|
||||
export const load = (({ params }) => {
|
||||
return {
|
||||
header: {
|
||||
title: m.albums,
|
||||
title: LL.albums(),
|
||||
searchCtx: SearchCtx.Content,
|
||||
} satisfies HeaderData,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import LL, { locale } from "$i18n/i18n-svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
import { searchTerm } from "$lib/stores/layout";
|
||||
|
@ -23,8 +24,8 @@
|
|||
{#each data.album.artists as artist}
|
||||
<AvatarBadge link={artistLink(artist)} imageUrl={artist.imageUrl} bold />
|
||||
{/each}
|
||||
<span>{formatDateStr(data.album.releaseDate)}</span>
|
||||
<span>11 tracks, 31:23</span>
|
||||
<span>{formatDateStr(data.album.releaseDate, $locale)}</span>
|
||||
<span>{$LL.n_tracks(11)}, 31:23</span>
|
||||
</div>
|
||||
</ContentHeader>
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import * as m from "$paraglide/messages";
|
||||
import type { HeaderData } from "$lib/util/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
import { i18nObject } from "$i18n/i18n-util";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { locale } = await parent();
|
||||
const LL = i18nObject(locale);
|
||||
|
||||
export const load = (({ params }) => {
|
||||
return {
|
||||
header: {
|
||||
title: m.artists,
|
||||
title: LL.artists(),
|
||||
} satisfies HeaderData,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
import { searchTerm } from "$lib/stores/layout";
|
||||
|
@ -20,10 +20,10 @@
|
|||
{#if !$searchTerm}
|
||||
<TrackList
|
||||
{tracks}
|
||||
name={m.top_tracks_of({ name: data.artist.name })}
|
||||
name={$LL.top_tracks_of(data.artist.name)}
|
||||
view={ListView.Catalog}
|
||||
/>
|
||||
<button class="btn btn-sm btn-outline mt-2 mb-4">{m.all_tracks()}</button>
|
||||
<button class="btn btn-sm btn-outline mt-2 mb-4">{$LL.all_tracks()}</button>
|
||||
{/if}
|
||||
<AlbumGrid {albums} searchTerm={$searchTerm} />
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import * as m from "$paraglide/messages";
|
||||
import type { HeaderData } from "$lib/util/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
import { i18nObject } from "$i18n/i18n-util";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { locale } = await parent();
|
||||
const LL = i18nObject(locale);
|
||||
|
||||
export const load = (({ params }) => {
|
||||
return {
|
||||
header: {
|
||||
title: m.playlists,
|
||||
title: LL.title(),
|
||||
} satisfies HeaderData,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
import { searchTerm } from "$lib/stores/layout";
|
||||
|
@ -31,7 +32,7 @@
|
|||
<div class="badges">
|
||||
<AvatarBadge link={userLink("Spotify", false)} bold />
|
||||
<span>2023</span>
|
||||
<span>11 tracks, 31:23</span>
|
||||
<span>{$LL.n_tracks(11)}, 31:23</span>
|
||||
</div>
|
||||
</ContentHeader>
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import * as m from "$paraglide/messages";
|
||||
import { SearchCtx, type HeaderData } from "$lib/util/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
import { i18nObject } from "$i18n/i18n-util";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { locale } = await parent();
|
||||
const LL = i18nObject(locale);
|
||||
|
||||
export const load = (({ params }) => {
|
||||
return {
|
||||
header: {
|
||||
title: m.search,
|
||||
title: LL.search(),
|
||||
searchCtx: SearchCtx.Global,
|
||||
} satisfies HeaderData,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import * as m from "$paraglide/messages";
|
||||
import LL from "$i18n/i18n-svelte";
|
||||
import ContentHeader from "$lib/components/header/ContentHeader.svelte";
|
||||
import SimpleWrapper from "$lib/components/layout/SimpleWrapper.svelte";
|
||||
import TrackList from "$lib/components/list/TrackList.svelte";
|
||||
|
@ -14,16 +14,16 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<ContentHeader title={m.tracks()} imageUrl="">
|
||||
<ContentHeader title={$LL.tracks()} imageUrl="">
|
||||
<div class="badges">
|
||||
<span>11 tracks, 31:23</span>
|
||||
<span>{$LL.n_tracks(11)}, 31:23</span>
|
||||
</div>
|
||||
</ContentHeader>
|
||||
|
||||
<SimpleWrapper>
|
||||
<TrackList
|
||||
{tracks}
|
||||
name={m.tracks()}
|
||||
name={$LL.tracks()}
|
||||
searchTerm={$searchTerm}
|
||||
view={ListView.Playlist}
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import * as m from "$paraglide/messages";
|
||||
import { LinkType, type HeaderData, SearchCtx } from "$lib/util/types";
|
||||
import type { PageLoad } from "./$types";
|
||||
import { i18nObject } from "$i18n/i18n-util";
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { locale } = await parent();
|
||||
const LL = i18nObject(locale);
|
||||
|
||||
export const load = (({ params }) => {
|
||||
return {
|
||||
header: {
|
||||
title: m.tracks,
|
||||
title: LL.tracks(),
|
||||
playLink: {
|
||||
title: "Tracks",
|
||||
title: LL.tracks(),
|
||||
linkType: LinkType.Playlist,
|
||||
id: "",
|
||||
},
|
||||
|
@ -15,4 +18,4 @@ export const load = (({ params }) => {
|
|||
searchCtx: SearchCtx.Content,
|
||||
} satisfies HeaderData,
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
|
||||
html {
|
||||
scrollbar-color: hsl(var(--bc) / 0.4) transparent;
|
||||
scrollbar-color: oklch(var(--bc) / 0.4) transparent;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
@ -17,11 +17,11 @@ html {
|
|||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: hsl(var(--bc) / 0.4);
|
||||
background-color: oklch(var(--bc) / 0.4);
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid hsl(var(--bc));
|
||||
outline: 2px solid oklch(var(--bc));
|
||||
}
|
||||
|
||||
.bg-cover {
|
||||
|
@ -60,8 +60,8 @@ html {
|
|||
}
|
||||
|
||||
mark {
|
||||
background-color: hsl(var(--p));
|
||||
color: hsl(var(--pc));
|
||||
background-color: oklch(var(--p));
|
||||
color: oklch(var(--pc));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><defs><linearGradient xlink:href="#a" id="b" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="matrix(2.63348 0 0 2.63348 -117.324 -128.325)" gradientUnits="userSpaceOnUse"/><linearGradient id="a"><stop offset="0" style="stop-color:#0035ff;stop-opacity:1"/><stop offset="1" style="stop-color:#00eaff;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#a" id="c" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="scale(-2.63348) rotate(-60 -52.86 95.708)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#a" id="d" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="scale(-2.63348) rotate(60 97.432 -46.988)" gradientUnits="userSpaceOnUse"/></defs><path d="M88.727 55.007 40.399 82.91V50.36" style="fill:none;stroke:url(#b);stroke-width:14.4841;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/><path d="M26.085 81.313V25.509l28.19 16.275" style="fill:none;stroke:url(#c);stroke-width:14.4841;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/><path d="m34.615 13.97 48.328 27.902-28.19 16.275" style="fill:none;stroke:url(#d);stroke-width:14.4841;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><defs><linearGradient xlink:href="#a" id="b" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="matrix(2.63348 0 0 2.63348 -117.324 -128.325)" gradientUnits="userSpaceOnUse"/><linearGradient id="a"><stop offset="0" style="stop-color:#16b5ab;stop-opacity:1"/><stop offset="1" style="stop-color:#00eaff;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#a" id="c" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="scale(-2.63348) rotate(-60 -52.86 95.708)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#a" id="d" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="scale(-2.63348) rotate(60 97.432 -46.988)" gradientUnits="userSpaceOnUse"/></defs><path d="M88.727 55.007 40.399 82.91V50.36" style="fill:none;stroke:url(#b);stroke-width:11.49784008;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" transform="matrix(1.10449 0 0 1.10449 -5.998 -5.061)"/><path d="M26.085 81.313V25.509l28.19 16.275" style="fill:none;stroke:url(#c);stroke-width:11.49784008;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" transform="matrix(1.10449 0 0 1.10449 -5.998 -5.061)"/><path d="m34.615 13.97 48.328 27.902-28.19 16.275" style="fill:none;stroke:url(#d);stroke-width:11.49784008;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" transform="matrix(1.10449 0 0 1.10449 -5.998 -5.061)"/></svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><defs><linearGradient xlink:href="#a" id="b" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="translate(-89.437 -98.604) scale(2.19457)" gradientUnits="userSpaceOnUse"/><linearGradient id="a"><stop offset="0" style="stop-color:#0035ff;stop-opacity:1"/><stop offset="1" style="stop-color:#00eaff;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#a" id="c" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="rotate(120 123.026 73.548) scale(2.19457)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#a" id="d" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="rotate(-120 66.107 125.211) scale(2.19457)" gradientUnits="userSpaceOnUse"/></defs><circle cx="50" cy="50" r="50" style="fill:#1a1e25;stroke-width:2.6382"/><path d="M82.272 54.173 42 77.424V50.3" style="fill:none;stroke:url(#b);stroke-width:12.0701;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/><path d="M30.071 76.094V29.591l23.491 13.563" style="fill:none;stroke:url(#c);stroke-width:12.0701;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/><path d="m37.18 19.975 40.272 23.251L53.961 56.79" style="fill:none;stroke:url(#d);stroke-width:12.0701;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><defs><linearGradient xlink:href="#a" id="b" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="translate(-89.437 -98.604) scale(2.19457)" gradientUnits="userSpaceOnUse"/><linearGradient id="a"><stop offset="0" style="stop-color:#16b5ab;stop-opacity:1"/><stop offset="1" style="stop-color:#00eaff;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#a" id="c" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="rotate(120 123.026 73.548) scale(2.19457)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#a" id="d" x1="75.381" x2="56.756" y1="72.556" y2="62.604" gradientTransform="rotate(-120 66.107 125.211) scale(2.19457)" gradientUnits="userSpaceOnUse"/></defs><circle cx="50" cy="50" r="50" style="fill:#1a1e25;stroke-width:2.6382"/><path d="M82.272 54.173 42 77.424V50.3" style="fill:none;stroke:url(#b);stroke-width:9.58154663;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/><path d="M30.071 76.094V29.591l23.491 13.563" style="fill:none;stroke:url(#c);stroke-width:9.58154663;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/><path d="m37.18 19.975 40.272 23.251L53.961 56.79" style="fill:none;stroke:url(#d);stroke-width:9.58154663;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/></svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -1,5 +1,5 @@
|
|||
import adapter from "@sveltejs/adapter-static";
|
||||
import { vitePreprocess } from "@sveltejs/kit/vite";
|
||||
import adapter from "@sveltejs/adapter-node";
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
import { preprocessMeltUI } from "@melt-ui/pp";
|
||||
import sequence from "svelte-sequential-preprocessor";
|
||||
|
||||
|
@ -12,12 +12,11 @@ const config = {
|
|||
kit: {
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter({
|
||||
fallback: "index.html",
|
||||
// precompress: true,
|
||||
precompress: true,
|
||||
}),
|
||||
|
||||
alias: {
|
||||
$paraglide: "./src/paraglide",
|
||||
$i18n: "./src/i18n",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@ module.exports = {
|
|||
"--rounded-btn": "1.9rem",
|
||||
},
|
||||
"tiraya-light": {
|
||||
primary: "#0065ff",
|
||||
primary: "#11c2bf",
|
||||
secondary: "#1e3ca1",
|
||||
accent: "#d99330",
|
||||
neutral: "#181a2a",
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "Bundler"
|
||||
"strict": true
|
||||
}
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import { sveltekit } from "@sveltejs/kit/vite";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { paraglide } from "@inlang/paraglide-js-adapter-vite";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
sveltekit(),
|
||||
paraglide({ project: "./project.inlang", outdir: "./src/paraglide" }),
|
||||
],
|
||||
plugins: [sveltekit()],
|
||||
test: {
|
||||
include: ["src/**/*.{test,spec}.{js,ts}"],
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue