From f9159741a0f834251566ded8f85bc0b45ea67ed5 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 19 May 2026 15:20:13 +0200 Subject: [PATCH] chore(mana): music + apps/mukke aus unified-App entfernen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mukke ist seit 2026-05-19 als Standalone-App live (mukke.mana.how + mukke-api.mana.how, Repo git.mana.how/till/mukke) mit umfassenderem Feature-Set (Studio, Wavesurfer, Lyrics-Sync, Beats, LRC/SRT/JSON- Export, ID3-Extract, S3-Streaming). Modul + alte Landing können raus. Entfernt: - apps/mana/apps/web/src/routes/(app)/music/ (alle 6 Routes) - apps/mana/apps/web/src/lib/modules/music/ (Stores, Queries, Collections, Tools, Types, Views, Components) - apps/mana/apps/web/src/lib/i18n/locales/music/ (DE/EN/ES/FR/IT) - apps/mana/apps/web/src/lib/search/providers/music.ts - apps/mana/apps/web/src/lib/components/dashboard/widgets/MusicLibraryWidget.svelte - apps/mukke/ (alte Landing + shared types-Package — Standalone hat beides selbst; VISUALIZER_CONCEPT.md + ALTERNATIVES.md vorab nach mukke/docs/ ins Standalone-Repo migriert) Aktualisiert (Music-Refs raus): - module-registry.ts (musicModuleConfig) - module-registry.test.ts (music-Tabellen-Expectation) - cross-app-queries.ts (useMusicStats + MusicStats-Interface) - tools/init.ts (musicTools-Init) - search/providers/index.ts (registerLazy 'music') - app-registry/apps.ts (registerApp 'music' + MusicNotes-Icon-Import) - packages/shared-branding/src/mana-apps.ts (music-Eintrag) - hooks.server.ts (Allowlist) - types/dashboard.ts (WidgetType 'music-library' + RequiredBackend) - types/dashboard.test.ts (Erwartung 'music-library') - stores/dashboard.svelte.ts (Widget-Default-Liste) - splitscreen/registry.ts - components/dashboard/widget-registry.ts NICHT angefasst (mit Absicht): - data/database.ts db.version(1).stores — Schema-Snapshot ist frozen (gleiche Konvention wie für cards/quotes). Tabellen (songs, mukkePlaylists, playlistSongs, mukkeProjects, markers, songTags) bleiben im IndexedDB-Schema, werden aber nicht mehr beschrieben. Bei Bedarf später ein db.version(N) mit `songs: null` etc. nachschieben. - modules/events/discovery/types.ts 'music' (Event-Kategorie, generisch) - data/time-blocks/types.ts 'music' (TimeBlock-Kategorie, generisch) - shared-ai/tools/schemas.ts 'music' (Event-Discovery-Enum) - packages/shared-branding/src/app-icons.ts APP_ICONS.music (für Native-PNG-Generator, harmlos) Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/mana/apps/web/src/hooks.server.ts | 1 - .../apps/web/src/lib/app-registry/apps.ts | 12 - .../components/dashboard/widget-registry.ts | 2 - .../widgets/MusicLibraryWidget.svelte | 71 --- .../web/src/lib/data/cross-app-queries.ts | 36 -- .../web/src/lib/data/module-registry.test.ts | 1 - .../apps/web/src/lib/data/module-registry.ts | 2 - apps/mana/apps/web/src/lib/data/tools/init.ts | 2 - .../web/src/lib/i18n/locales/music/de.json | 20 - .../web/src/lib/i18n/locales/music/en.json | 20 - .../web/src/lib/i18n/locales/music/es.json | 20 - .../web/src/lib/i18n/locales/music/fr.json | 20 - .../web/src/lib/i18n/locales/music/it.json | 20 - .../web/src/lib/modules/music/ListView.svelte | 400 -------------- .../web/src/lib/modules/music/collections.ts | 41 -- .../apps/web/src/lib/modules/music/index.ts | 52 -- .../src/lib/modules/music/module.config.ts | 13 - .../apps/web/src/lib/modules/music/queries.ts | 255 --------- .../modules/music/stores/library.svelte.ts | 93 ---- .../lib/modules/music/stores/player.svelte.ts | 275 ---------- .../modules/music/stores/playlists.svelte.ts | 93 ---- .../modules/music/stores/projects.svelte.ts | 40 -- .../lib/modules/music/stores/tags.svelte.ts | 19 - .../apps/web/src/lib/modules/music/tools.ts | 17 - .../apps/web/src/lib/modules/music/types.ts | 122 ----- .../lib/modules/music/views/DetailView.svelte | 183 ------- .../web/src/lib/search/providers/index.ts | 1 - .../web/src/lib/search/providers/music.ts | 109 ---- .../apps/web/src/lib/splitscreen/registry.ts | 1 - .../web/src/lib/stores/dashboard.svelte.ts | 1 - .../apps/web/src/lib/types/dashboard.test.ts | 2 - apps/mana/apps/web/src/lib/types/dashboard.ts | 11 - .../web/src/routes/(app)/music/+layout.svelte | 26 - .../web/src/routes/(app)/music/+page.svelte | 138 ----- .../routes/(app)/music/library/+page.svelte | 251 --------- .../routes/(app)/music/playlists/+page.svelte | 182 ------- .../(app)/music/playlists/[id]/+page.svelte | 230 -------- .../routes/(app)/music/projects/+page.svelte | 181 ------- apps/mukke/CLAUDE.md | 191 ------- apps/mukke/apps/landing/astro.config.mjs | 7 - apps/mukke/apps/landing/package.json | 20 - .../apps/landing/src/layouts/Layout.astro | 61 --- apps/mukke/apps/landing/src/pages/index.astro | 212 -------- apps/mukke/apps/landing/tsconfig.json | 6 - apps/mukke/docs/VISUALIZER_ALTERNATIVES.md | 417 --------------- apps/mukke/docs/VISUALIZER_CONCEPT.md | 490 ------------------ apps/mukke/package.json | 8 - apps/mukke/packages/shared/package.json | 17 - apps/mukke/packages/shared/src/index.ts | 1 - apps/mukke/packages/shared/src/types/beat.ts | 40 -- .../mukke/packages/shared/src/types/export.ts | 57 -- apps/mukke/packages/shared/src/types/index.ts | 7 - .../mukke/packages/shared/src/types/lyrics.ts | 55 -- .../mukke/packages/shared/src/types/marker.ts | 49 -- .../packages/shared/src/types/playlist.ts | 25 - .../packages/shared/src/types/project.ts | 18 - apps/mukke/packages/shared/src/types/song.ts | 77 --- apps/mukke/packages/shared/tsconfig.json | 15 - packages/shared-branding/src/mana-apps.ts | 17 - 59 files changed, 4753 deletions(-) delete mode 100644 apps/mana/apps/web/src/lib/components/dashboard/widgets/MusicLibraryWidget.svelte delete mode 100644 apps/mana/apps/web/src/lib/i18n/locales/music/de.json delete mode 100644 apps/mana/apps/web/src/lib/i18n/locales/music/en.json delete mode 100644 apps/mana/apps/web/src/lib/i18n/locales/music/es.json delete mode 100644 apps/mana/apps/web/src/lib/i18n/locales/music/fr.json delete mode 100644 apps/mana/apps/web/src/lib/i18n/locales/music/it.json delete mode 100644 apps/mana/apps/web/src/lib/modules/music/ListView.svelte delete mode 100644 apps/mana/apps/web/src/lib/modules/music/collections.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/index.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/module.config.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/queries.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/stores/player.svelte.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/stores/tags.svelte.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/tools.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/types.ts delete mode 100644 apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte delete mode 100644 apps/mana/apps/web/src/lib/search/providers/music.ts delete mode 100644 apps/mana/apps/web/src/routes/(app)/music/+layout.svelte delete mode 100644 apps/mana/apps/web/src/routes/(app)/music/+page.svelte delete mode 100644 apps/mana/apps/web/src/routes/(app)/music/library/+page.svelte delete mode 100644 apps/mana/apps/web/src/routes/(app)/music/playlists/+page.svelte delete mode 100644 apps/mana/apps/web/src/routes/(app)/music/playlists/[id]/+page.svelte delete mode 100644 apps/mana/apps/web/src/routes/(app)/music/projects/+page.svelte delete mode 100644 apps/mukke/CLAUDE.md delete mode 100644 apps/mukke/apps/landing/astro.config.mjs delete mode 100644 apps/mukke/apps/landing/package.json delete mode 100644 apps/mukke/apps/landing/src/layouts/Layout.astro delete mode 100644 apps/mukke/apps/landing/src/pages/index.astro delete mode 100644 apps/mukke/apps/landing/tsconfig.json delete mode 100644 apps/mukke/docs/VISUALIZER_ALTERNATIVES.md delete mode 100644 apps/mukke/docs/VISUALIZER_CONCEPT.md delete mode 100644 apps/mukke/package.json delete mode 100644 apps/mukke/packages/shared/package.json delete mode 100644 apps/mukke/packages/shared/src/index.ts delete mode 100644 apps/mukke/packages/shared/src/types/beat.ts delete mode 100644 apps/mukke/packages/shared/src/types/export.ts delete mode 100644 apps/mukke/packages/shared/src/types/index.ts delete mode 100644 apps/mukke/packages/shared/src/types/lyrics.ts delete mode 100644 apps/mukke/packages/shared/src/types/marker.ts delete mode 100644 apps/mukke/packages/shared/src/types/playlist.ts delete mode 100644 apps/mukke/packages/shared/src/types/project.ts delete mode 100644 apps/mukke/packages/shared/src/types/song.ts delete mode 100644 apps/mukke/packages/shared/tsconfig.json diff --git a/apps/mana/apps/web/src/hooks.server.ts b/apps/mana/apps/web/src/hooks.server.ts index 0bb6796cf..6ca70942e 100644 --- a/apps/mana/apps/web/src/hooks.server.ts +++ b/apps/mana/apps/web/src/hooks.server.ts @@ -144,7 +144,6 @@ const APP_SUBDOMAINS = new Set([ 'storage', 'presi', 'photos', - 'music', 'picture', 'calc', 'inventory', diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index bacc1482c..a6505d4eb 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -23,7 +23,6 @@ import { Clock, Quotes, Image, - MusicNotes, Camera, HardDrives, Presentation, @@ -601,17 +600,6 @@ registerApp({ }, }); -registerApp({ - id: 'music', - name: 'Music', - color: '#F97316', - icon: MusicNotes, - views: { - list: { load: () => import('$lib/modules/music/ListView.svelte') }, - detail: { load: () => import('$lib/modules/music/views/DetailView.svelte') }, - }, -}); - registerApp({ id: 'photos', name: 'Photos', diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts index 7cfe975d5..f065bdf9e 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts +++ b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts @@ -20,7 +20,6 @@ import QuoteWidget from './widgets/QuoteWidget.svelte'; import PictureRecentWidget from './widgets/PictureRecentWidget.svelte'; import ClockTimersWidget from './widgets/ClockTimersWidget.svelte'; import StorageUsageWidget from './widgets/StorageUsageWidget.svelte'; -import MusicLibraryWidget from './widgets/MusicLibraryWidget.svelte'; import PresiDecksWidget from './widgets/PresiDecksWidget.svelte'; // Phase 4: Unified app widgets (direct Dexie queries, internal routing) @@ -49,7 +48,6 @@ export const widgetComponents: Record = { 'picture-recent': PictureRecentWidget, 'clock-timers': ClockTimersWidget, 'storage-usage': StorageUsageWidget, - 'music-library': MusicLibraryWidget, 'presi-decks': PresiDecksWidget, 'active-timer': ActiveTimerWidget, 'day-timeline': DayTimelineWidget, diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/MusicLibraryWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/MusicLibraryWidget.svelte deleted file mode 100644 index 81ec1a594..000000000 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/MusicLibraryWidget.svelte +++ /dev/null @@ -1,71 +0,0 @@ - - -
-
-

- 🎵 - {$_('dashboard.widgets.music.title')} -

-
- - {#if stats.loading} -
- {#each Array(3) as _} -
- {/each} -
- {:else} -
-
- {stats.value.totalSongs} - Songs -
-
- {stats.value.totalPlaylists} - Playlists -
-
- {stats.value.favoriteCount} - -
-
- - {#if stats.value.recentSongs.length > 0} -
- {#each stats.value.recentSongs as song (song.id)} -
- 🎵 -
-

{song.title}

- {#if song.artist} -

{song.artist}

- {/if} -
- {#if song.duration} - - {formatDuration(song.duration)} - - {/if} -
- {/each} -
- {/if} - -

Music

- {/if} -
diff --git a/apps/mana/apps/web/src/lib/data/cross-app-queries.ts b/apps/mana/apps/web/src/lib/data/cross-app-queries.ts index 60e7734fb..ccc107892 100644 --- a/apps/mana/apps/web/src/lib/data/cross-app-queries.ts +++ b/apps/mana/apps/web/src/lib/data/cross-app-queries.ts @@ -17,7 +17,6 @@ import type { LocalFavorite } from '$lib/modules/quotes/types'; import type { LocalImage } from '$lib/modules/picture/types'; import type { LocalAlarm, LocalCountdownTimer } from '$lib/modules/times/types'; import type { LocalFile } from '$lib/modules/storage/types'; -import type { LocalSong, LocalPlaylist } from '$lib/modules/music/types'; import type { LocalDeck as LocalPresiDeck } from '$lib/modules/presi/types'; // ─── Todo Queries ─────────────────────────────────────────── @@ -222,41 +221,6 @@ export function useStorageStats() { ); } -// ─── Music Queries ────────────────────────────────────────── - -interface MusicStats { - totalSongs: number; - totalPlaylists: number; - favoriteCount: number; - recentSongs: LocalSong[]; -} - -/** Music library stats + recent songs. */ -export function useMusicStats() { - return useLiveQueryWithDefault( - async (): Promise => { - const songs = await db.table('songs').toArray(); - const playlists = await db.table('mukkePlaylists').toArray(); - const activeSongs = songs.filter((s) => !s.deletedAt); - const activePlaylists = playlists.filter((p) => !p.deletedAt); - // title is encrypted on disk; the dashboard widget renders it - // for the recent-songs list, so decrypt the small slice we - // surface (counts only need plaintext flags). - const recentRaw = activeSongs - .sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '')) - .slice(0, 5); - const recent = await decryptRecords('songs', recentRaw); - return { - totalSongs: activeSongs.length, - totalPlaylists: activePlaylists.length, - favoriteCount: activeSongs.filter((s) => s.favorite).length, - recentSongs: recent, - }; - }, - { totalSongs: 0, totalPlaylists: 0, favoriteCount: 0, recentSongs: [] as LocalSong[] } - ); -} - // ─── Presi Queries ────────────────────────────────────────── /** Recent presentation decks. */ diff --git a/apps/mana/apps/web/src/lib/data/module-registry.test.ts b/apps/mana/apps/web/src/lib/data/module-registry.test.ts index 2ebe87872..08fcfb511 100644 --- a/apps/mana/apps/web/src/lib/data/module-registry.test.ts +++ b/apps/mana/apps/web/src/lib/data/module-registry.test.ts @@ -200,7 +200,6 @@ describe('module-registry — snapshot', () => { chat: ['conversations', 'messages', 'chatTemplates', 'conversationTags'], picture: ['images', 'boards', 'boardItems', 'imageTags'], quotes: ['quotesFavorites', 'quotesLists', 'quotesListTags', 'customQuotes'], - music: ['songs', 'mukkePlaylists', 'playlistSongs', 'mukkeProjects', 'markers', 'songTags'], storage: ['files', 'storageFolders', 'fileTags'], presi: ['presiDecks', 'slides', 'presiDeckTags'], inventory: ['invCollections', 'invItems', 'invLocations', 'invCategories', 'invItemTags'], diff --git a/apps/mana/apps/web/src/lib/data/module-registry.ts b/apps/mana/apps/web/src/lib/data/module-registry.ts index a238419bd..273ae7e05 100644 --- a/apps/mana/apps/web/src/lib/data/module-registry.ts +++ b/apps/mana/apps/web/src/lib/data/module-registry.ts @@ -57,7 +57,6 @@ import { contactsModuleConfig } from '$lib/modules/contacts/module.config'; import { chatModuleConfig } from '$lib/modules/chat/module.config'; import { pictureModuleConfig } from '$lib/modules/picture/module.config'; import { quotesModuleConfig } from '$lib/modules/quotes/module.config'; -import { musicModuleConfig } from '$lib/modules/music/module.config'; import { storageModuleConfig } from '$lib/modules/storage/module.config'; import { presiModuleConfig } from '$lib/modules/presi/module.config'; import { inventoryModuleConfig } from '$lib/modules/inventory/module.config'; @@ -110,7 +109,6 @@ export const MODULE_CONFIGS: readonly ModuleConfig[] = [ chatModuleConfig, pictureModuleConfig, quotesModuleConfig, - musicModuleConfig, storageModuleConfig, presiModuleConfig, inventoryModuleConfig, diff --git a/apps/mana/apps/web/src/lib/data/tools/init.ts b/apps/mana/apps/web/src/lib/data/tools/init.ts index 5d6c4b244..166d9dad6 100644 --- a/apps/mana/apps/web/src/lib/data/tools/init.ts +++ b/apps/mana/apps/web/src/lib/data/tools/init.ts @@ -17,7 +17,6 @@ import { financeTools } from '$lib/modules/finance/tools'; import { dreamsTools } from '$lib/modules/dreams/tools'; import { timesTools } from '$lib/modules/times/tools'; import { socialEventsTools } from '$lib/modules/events/tools'; -import { musicTools } from '$lib/modules/music/tools'; import { storageTools } from '$lib/modules/storage/tools'; import { chatTools } from '$lib/modules/chat/tools'; import { skilltreeTools } from '$lib/modules/skilltree/tools'; @@ -63,7 +62,6 @@ export function initTools(): void { registerTools(dreamsTools); registerTools(timesTools); registerTools(socialEventsTools); - registerTools(musicTools); registerTools(storageTools); registerTools(chatTools); registerTools(skilltreeTools); diff --git a/apps/mana/apps/web/src/lib/i18n/locales/music/de.json b/apps/mana/apps/web/src/lib/i18n/locales/music/de.json deleted file mode 100644 index 1d4864352..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/music/de.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "detail": { - "not_found": "Song nicht gefunden", - "confirm_delete": "Song wirklich löschen?", - "toast_deleted": "Song gelöscht", - "placeholder_title": "Titel...", - "title_fallback": "Ohne Titel", - "prop_artist": "Künstler", - "prop_artist_placeholder": "Unbekannt", - "prop_album": "Album", - "prop_genre": "Genre", - "prop_year": "Jahr", - "prop_bpm": "BPM", - "prop_duration": "Dauer", - "prop_play_count": "Wiedergaben", - "meta_created": "Erstellt: {date}", - "meta_updated": "Bearbeitet: {date}", - "meta_last_played": "Zuletzt gehört: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/music/en.json b/apps/mana/apps/web/src/lib/i18n/locales/music/en.json deleted file mode 100644 index 32e7af59c..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/music/en.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "detail": { - "not_found": "Song not found", - "confirm_delete": "Really delete this song?", - "toast_deleted": "Song deleted", - "placeholder_title": "Title...", - "title_fallback": "Untitled", - "prop_artist": "Artist", - "prop_artist_placeholder": "Unknown", - "prop_album": "Album", - "prop_genre": "Genre", - "prop_year": "Year", - "prop_bpm": "BPM", - "prop_duration": "Duration", - "prop_play_count": "Plays", - "meta_created": "Created: {date}", - "meta_updated": "Edited: {date}", - "meta_last_played": "Last played: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/music/es.json b/apps/mana/apps/web/src/lib/i18n/locales/music/es.json deleted file mode 100644 index 9645c67a9..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/music/es.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "detail": { - "not_found": "Canción no encontrada", - "confirm_delete": "¿Eliminar realmente esta canción?", - "toast_deleted": "Canción eliminada", - "placeholder_title": "Título...", - "title_fallback": "Sin título", - "prop_artist": "Artista", - "prop_artist_placeholder": "Desconocido", - "prop_album": "Álbum", - "prop_genre": "Género", - "prop_year": "Año", - "prop_bpm": "BPM", - "prop_duration": "Duración", - "prop_play_count": "Reproducciones", - "meta_created": "Creado: {date}", - "meta_updated": "Editado: {date}", - "meta_last_played": "Última escucha: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/music/fr.json b/apps/mana/apps/web/src/lib/i18n/locales/music/fr.json deleted file mode 100644 index dd988db9b..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/music/fr.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "detail": { - "not_found": "Morceau introuvable", - "confirm_delete": "Vraiment supprimer ce morceau ?", - "toast_deleted": "Morceau supprimé", - "placeholder_title": "Titre...", - "title_fallback": "Sans titre", - "prop_artist": "Artiste", - "prop_artist_placeholder": "Inconnu", - "prop_album": "Album", - "prop_genre": "Genre", - "prop_year": "Année", - "prop_bpm": "BPM", - "prop_duration": "Durée", - "prop_play_count": "Lectures", - "meta_created": "Créé : {date}", - "meta_updated": "Modifié : {date}", - "meta_last_played": "Dernière écoute : {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/music/it.json b/apps/mana/apps/web/src/lib/i18n/locales/music/it.json deleted file mode 100644 index 24515834c..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/music/it.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "detail": { - "not_found": "Brano non trovato", - "confirm_delete": "Eliminare davvero questo brano?", - "toast_deleted": "Brano eliminato", - "placeholder_title": "Titolo...", - "title_fallback": "Senza titolo", - "prop_artist": "Artista", - "prop_artist_placeholder": "Sconosciuto", - "prop_album": "Album", - "prop_genre": "Genere", - "prop_year": "Anno", - "prop_bpm": "BPM", - "prop_duration": "Durata", - "prop_play_count": "Riproduzioni", - "meta_created": "Creato: {date}", - "meta_updated": "Modificato: {date}", - "meta_last_played": "Ultimo ascolto: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/modules/music/ListView.svelte b/apps/mana/apps/web/src/lib/modules/music/ListView.svelte deleted file mode 100644 index eb7a173b8..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/ListView.svelte +++ /dev/null @@ -1,400 +0,0 @@ - - - -
- - - - {#if dragActive} -
- - Musik ablegen -
- {/if} - - s.id} emptyTitle="Noch nichts gehört"> - {#snippet header()} - {songs.length} Songs - {playlists.length} Playlists - {favorites.length} Favoriten - {/snippet} - - {#snippet toolbar()} - -
- - - {#if uploadFiles.length > 0} -
- {#each uploadFiles as uf, i (uf.file.name + i)} -
- {uf.file.name} - {#if uf.status === 'uploading'} - - {:else if uf.status === 'success'} - - {:else if uf.status === 'error'} - - {:else} - - {/if} -
- {/each} -
- {/if} -
- {/snippet} - - {#snippet listHeader()} -

Zuletzt gehört

- {/snippet} - - {#snippet item(song)} - - {/snippet} -
-
- - diff --git a/apps/mana/apps/web/src/lib/modules/music/collections.ts b/apps/mana/apps/web/src/lib/modules/music/collections.ts deleted file mode 100644 index 2ca6d21bb..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/collections.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Music module — collection accessors and guest seed data. - * - * Dexie table names kept as mukkePlaylists/mukkeProjects for backward compat. - */ - -import { db } from '$lib/data/database'; -import type { - LocalSong, - LocalPlaylist, - LocalPlaylistSong, - LocalProject, - LocalMarker, -} from './types'; - -// ─── Collection Accessors ────────────────────────────────── - -export const songTable = db.table('songs'); -export const musicPlaylistTable = db.table('mukkePlaylists'); -export const playlistSongTable = db.table('playlistSongs'); -export const musicProjectTable = db.table('mukkeProjects'); -export const markerTable = db.table('markers'); - -// ─── Guest Seed ──────────────────────────────────────────── - -const DEMO_PLAYLIST_ID = 'demo-favorites'; - -export const MUSIC_GUEST_SEED = { - songs: [] as Record[], - mukkePlaylists: [ - { - id: DEMO_PLAYLIST_ID, - name: 'Meine Favoriten', - description: 'Deine Lieblingssongs.', - coverArtPath: null, - }, - ], - playlistSongs: [] as Record[], - mukkeProjects: [] as Record[], - markers: [] as Record[], -}; diff --git a/apps/mana/apps/web/src/lib/modules/music/index.ts b/apps/mana/apps/web/src/lib/modules/music/index.ts deleted file mode 100644 index 1e66447e7..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Music module — barrel exports. - */ - -export { libraryStore } from './stores/library.svelte'; -export { playlistsStore } from './stores/playlists.svelte'; -export { projectsStore } from './stores/projects.svelte'; -export { playerStore } from './stores/player.svelte'; -export { - useAllSongs, - useAllPlaylists, - useAllPlaylistSongs, - useAllProjects, - useMarkersByBeat, - toSong, - toPlaylist, - toProject, - searchSongs, - filterFavorites, - filterByArtist, - filterByAlbum, - filterByGenre, - getPlaylistSongs, - groupByArtist, - groupByAlbum, - groupByGenre, - computeStats, - formatDuration, -} from './queries'; -export { - songTable, - musicPlaylistTable, - playlistSongTable, - musicProjectTable, - markerTable, - MUSIC_GUEST_SEED, -} from './collections'; -export type { - LocalSong, - LocalPlaylist, - LocalPlaylistSong, - LocalProject, - LocalMarker, - Song, - Playlist, - Project, - Album, - Artist, - Genre, - LibraryStats, - RepeatMode, -} from './types'; diff --git a/apps/mana/apps/web/src/lib/modules/music/module.config.ts b/apps/mana/apps/web/src/lib/modules/music/module.config.ts deleted file mode 100644 index 46fb3706f..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/module.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ModuleConfig } from '$lib/data/module-registry'; - -export const musicModuleConfig: ModuleConfig = { - appId: 'music', - tables: [ - { name: 'songs' }, - { name: 'mukkePlaylists', syncName: 'playlists' }, - { name: 'playlistSongs' }, - { name: 'mukkeProjects', syncName: 'projects' }, - { name: 'markers' }, - { name: 'songTags' }, - ], -}; diff --git a/apps/mana/apps/web/src/lib/modules/music/queries.ts b/apps/mana/apps/web/src/lib/modules/music/queries.ts deleted file mode 100644 index 34c882fa0..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/queries.ts +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Reactive queries & pure helpers for Music — uses Dexie liveQuery on the unified DB. - */ - -import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte'; -import { deriveUpdatedAt } from '$lib/data/sync'; -import { db } from '$lib/data/database'; -import { scopedForModule } from '$lib/data/scope'; -import { decryptRecords } from '$lib/data/crypto'; -import type { - LocalSong, - LocalPlaylist, - LocalPlaylistSong, - LocalProject, - LocalMarker, - Song, - Playlist, - Project, - Album, - Artist, - Genre, -} from './types'; - -// ─── Type Converters ─────────────────────────────────────── - -export function toSong(local: LocalSong): Song { - return { - id: local.id, - title: local.title, - artist: local.artist ?? null, - album: local.album ?? null, - albumArtist: local.albumArtist ?? null, - genre: local.genre ?? null, - trackNumber: local.trackNumber ?? null, - year: local.year ?? null, - duration: local.duration ?? null, - storagePath: local.storagePath, - coverArtPath: local.coverArtPath ?? null, - fileSize: local.fileSize ?? null, - bpm: local.bpm ?? null, - favorite: local.favorite, - playCount: local.playCount, - lastPlayedAt: local.lastPlayedAt ?? null, - createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: deriveUpdatedAt(local), - }; -} - -export function toPlaylist(local: LocalPlaylist): Playlist { - return { - id: local.id, - name: local.name, - description: local.description ?? null, - coverArtPath: local.coverArtPath ?? null, - createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: deriveUpdatedAt(local), - }; -} - -export function toProject(local: LocalProject): Project { - return { - id: local.id, - title: local.title, - description: local.description ?? null, - songId: local.songId ?? null, - createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: deriveUpdatedAt(local), - }; -} - -// ─── Live Queries ────────────────────────────────────────── - -/** All songs, sorted by title. */ -export function useAllSongs() { - return useScopedLiveQuery(async () => { - const locals = await scopedForModule('music', 'songs').toArray(); - const visible = locals.filter((s) => !s.deletedAt); - // title is encrypted on disk; sort needs the plaintext value. - const decrypted = await decryptRecords('songs', visible); - return decrypted.map(toSong).sort((a, b) => a.title.localeCompare(b.title)); - }, []); -} - -/** All playlists, sorted by name. */ -export function useAllPlaylists() { - return useScopedLiveQuery(async () => { - const locals = await scopedForModule( - 'music', - 'mukkePlaylists' - ).toArray(); - const visible = locals.filter((p) => !p.deletedAt); - const decrypted = await decryptRecords('mukkePlaylists', visible); - return decrypted.map(toPlaylist).sort((a, b) => a.name.localeCompare(b.name)); - }, []); -} - -/** All playlist-song associations. */ -export function useAllPlaylistSongs() { - return useScopedLiveQuery(async () => { - const locals = await scopedForModule( - 'music', - 'playlistSongs' - ).toArray(); - return locals.filter((ps) => !ps.deletedAt); - }, []); -} - -/** All projects, sorted by title. */ -export function useAllProjects() { - return useScopedLiveQuery(async () => { - const locals = await scopedForModule('music', 'mukkeProjects').toArray(); - return locals - .filter((p) => !p.deletedAt) - .map(toProject) - .sort((a, b) => a.title.localeCompare(b.title)); - }, []); -} - -/** All markers for a given beat ID. */ -export function useMarkersByBeat(beatId: string) { - return useScopedLiveQuery(async () => { - const locals = await db.table('markers').where('beatId').equals(beatId).toArray(); - return locals.filter((m) => !m.deletedAt).sort((a, b) => a.startTime - b.startTime); - }, []); -} - -// ─── Pure Filter Functions ───────────────────────────────── - -/** Filter songs by search query across title, artist, album. */ -export function searchSongs(songs: Song[], query: string): Song[] { - if (!query.trim()) return songs; - const search = query.toLowerCase().trim(); - return songs.filter( - (s) => - s.title?.toLowerCase().includes(search) || - s.artist?.toLowerCase().includes(search) || - s.album?.toLowerCase().includes(search) - ); -} - -/** Filter songs to favorites only. */ -export function filterFavorites(songs: Song[]): Song[] { - return songs.filter((s) => s.favorite); -} - -/** Filter songs by artist. */ -export function filterByArtist(songs: Song[], artist: string): Song[] { - if (!artist) return songs; - return songs.filter((s) => s.artist === artist); -} - -/** Filter songs by album. */ -export function filterByAlbum(songs: Song[], album: string): Song[] { - if (!album) return songs; - return songs.filter((s) => s.album === album); -} - -/** Filter songs by genre. */ -export function filterByGenre(songs: Song[], genre: string): Song[] { - if (!genre) return songs; - return songs.filter((s) => s.genre === genre); -} - -/** Get songs for a playlist, sorted by sortOrder. */ -export function getPlaylistSongs( - songs: Song[], - playlistSongs: LocalPlaylistSong[], - playlistId: string -): Song[] { - const psForPlaylist = playlistSongs - .filter((ps) => ps.playlistId === playlistId) - .sort((a, b) => a.sortOrder - b.sortOrder); - return psForPlaylist - .map((ps) => songs.find((s) => s.id === ps.songId)) - .filter((s): s is Song => !!s); -} - -/** Group songs by artist. */ -export function groupByArtist(songs: Song[]): Album[] { - const map = new Map(); - const artistAlbums = new Map>(); - for (const s of songs) { - const key = s.artist || 'Unknown'; - if (!map.has(key)) { - map.set(key, { songCount: 0, albumCount: 0 }); - artistAlbums.set(key, new Set()); - } - map.get(key)!.songCount++; - if (s.album) artistAlbums.get(key)!.add(s.album); - } - return Array.from(map.entries()).map(([artist, data]) => ({ - album: artist, - albumArtist: artist, - year: null, - coverArtPath: null, - songCount: data.songCount, - })); -} - -/** Group songs by album. */ -export function groupByAlbum(songs: Song[]): Album[] { - const albumMap = new Map(); - for (const s of songs) { - const key = s.album || 'Unknown Album'; - if (!albumMap.has(key)) { - albumMap.set(key, { - album: key, - albumArtist: s.albumArtist || s.artist || 'Unknown', - year: s.year ?? null, - coverArtPath: s.coverArtPath ?? null, - songCount: 0, - }); - } - albumMap.get(key)!.songCount++; - } - return Array.from(albumMap.values()); -} - -/** Group songs by genre. */ -export function groupByGenre(songs: Song[]): Genre[] { - const genreMap = new Map(); - for (const s of songs) { - const key = s.genre || 'Unknown'; - genreMap.set(key, (genreMap.get(key) || 0) + 1); - } - return Array.from(genreMap.entries()).map(([genre, songCount]) => ({ genre, songCount })); -} - -/** Compute library stats from songs. */ -export function computeStats(songs: Song[]): { - totalSongs: number; - totalArtists: number; - totalAlbums: number; - totalGenres: number; - totalDuration: number; - totalPlays: number; -} { - const artists = new Set(songs.map((s) => s.artist).filter(Boolean)); - const albums = new Set(songs.map((s) => s.album).filter(Boolean)); - const genres = new Set(songs.map((s) => s.genre).filter(Boolean)); - return { - totalSongs: songs.length, - totalArtists: artists.size, - totalAlbums: albums.size, - totalGenres: genres.size, - totalDuration: songs.reduce((sum, s) => sum + (s.duration || 0), 0), - totalPlays: songs.reduce((sum, s) => sum + (s.playCount || 0), 0), - }; -} - -/** Format duration in seconds to m:ss. */ -export function formatDuration(seconds: number | null | undefined): string { - if (!seconds) return '0:00'; - return Math.floor(seconds / 60) + ':' + String(Math.floor(seconds % 60)).padStart(2, '0'); -} diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts deleted file mode 100644 index 0fb4e6dc1..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/stores/library.svelte.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Library Store — Mutations for songs - * - * Reads come from liveQuery hooks in queries.ts. - * Handles toggle favorite, delete, update metadata. - */ - -import { songTable } from '../collections'; -import { encryptRecord, decryptRecord } from '$lib/data/crypto'; -import { emitDomainEvent } from '$lib/data/events'; -import { createBlock } from '$lib/data/time-blocks/service'; -import { MusicEvents } from '@mana/shared-utils/analytics'; -import type { LocalSong } from '../types'; - -export const libraryStore = { - /** Toggle favorite — writes to IndexedDB instantly. */ - async toggleFavorite(id: string) { - const local = await songTable.get(id); - if (local) { - const newState = !local.favorite; - await songTable.update(id, { - favorite: newState, - }); - MusicEvents.songFavorited(newState); - } - }, - - /** Increment play count and create a listening TimeBlock. */ - async incrementPlayCount(id: string) { - const local = await songTable.get(id); - if (local) { - const now = new Date().toISOString(); - await songTable.update(id, { - playCount: (local.playCount || 0) + 1, - lastPlayedAt: now, - }); - - const decrypted = await decryptRecord('songs', { ...local }); - const title = decrypted?.title ?? 'Song'; - const artist = decrypted?.artist; - const endDate = local.duration - ? new Date(Date.now() + local.duration * 1000).toISOString() - : now; - - await createBlock({ - startDate: now, - endDate, - kind: 'logged', - type: 'listening', - sourceModule: 'music', - sourceId: id, - title: artist ? `${title} — ${artist}` : title, - color: '#d946ef', - }); - - MusicEvents.songPlayed(); - } - }, - - /** Update song metadata. */ - async updateMetadata( - id: string, - data: Partial< - Pick< - LocalSong, - 'title' | 'artist' | 'album' | 'albumArtist' | 'genre' | 'trackNumber' | 'year' | 'bpm' - > - > - ) { - const diff: Record = { - ...data, - }; - await encryptRecord('songs', diff); - await songTable.update(id, diff); - }, - - /** Soft-delete a song. */ - async delete(id: string) { - const now = new Date().toISOString(); - await songTable.update(id, { deletedAt: now }); - MusicEvents.songDeleted(); - }, - - /** Insert a song (e.g., after upload). */ - async insert(song: LocalSong) { - await encryptRecord('songs', song); - await songTable.add(song); - emitDomainEvent('SongAdded', 'music', 'songs', song.id, { - songId: song.id, - title: (song.title as string) ?? '', - }); - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/player.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/player.svelte.ts deleted file mode 100644 index 911dc383f..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/stores/player.svelte.ts +++ /dev/null @@ -1,275 +0,0 @@ -/** - * Player Store — Audio playback state management - * - * Manages the HTML5 Audio element, queue, shuffle, repeat modes. - * This is a runtime-only store (no IndexedDB persistence). - */ - -import type { Song, RepeatMode } from '../types'; - -interface PlayerState { - currentSong: Song | null; - isPlaying: boolean; - currentTime: number; - duration: number; - volume: number; - repeatMode: RepeatMode; - shuffleOn: boolean; - queue: Song[]; - originalQueue: Song[]; - currentIndex: number; - showFullPlayer: boolean; - error: string | null; -} - -function shuffleArray(arr: T[], keepIndex: number): T[] { - const result = [...arr]; - if (keepIndex >= 0 && keepIndex < result.length) { - [result[0], result[keepIndex]] = [result[keepIndex], result[0]]; - } - for (let i = result.length - 1; i > 1; i--) { - const j = 1 + Math.floor(Math.random() * i); - [result[i], result[j]] = [result[j], result[i]]; - } - return result; -} - -function createPlayerStore() { - let state = $state({ - currentSong: null, - isPlaying: false, - currentTime: 0, - duration: 0, - volume: 1, - repeatMode: 'off', - shuffleOn: false, - queue: [], - originalQueue: [], - currentIndex: 0, - showFullPlayer: false, - error: null, - }); - - let audio: HTMLAudioElement | null = null; - - if (typeof window !== 'undefined') { - audio = new Audio(); - audio.crossOrigin = 'anonymous'; - audio.addEventListener('timeupdate', () => { - state.currentTime = audio!.currentTime; - }); - audio.addEventListener('loadedmetadata', () => { - state.duration = audio!.duration; - }); - audio.addEventListener('ended', () => { - handleNext(); - }); - audio.addEventListener('error', () => { - state.error = 'Audiodatei konnte nicht geladen werden'; - state.isPlaying = false; - }); - } - - function getNextIndex(): number | null { - if (state.queue.length === 0) return null; - if (state.repeatMode === 'one') return state.currentIndex; - if (state.currentIndex < state.queue.length - 1) return state.currentIndex + 1; - if (state.repeatMode === 'all') return 0; - return null; - } - - function getPreviousIndex(): number | null { - if (state.queue.length === 0) return null; - if (state.repeatMode === 'one') return state.currentIndex; - if (state.currentIndex > 0) return state.currentIndex - 1; - if (state.repeatMode === 'all') return state.queue.length - 1; - return null; - } - - function updateMediaSession(song: Song) { - if (typeof navigator !== 'undefined' && 'mediaSession' in navigator) { - navigator.mediaSession.metadata = new MediaMetadata({ - title: song.title, - artist: song.artist || 'Unknown', - album: song.album || '', - }); - navigator.mediaSession.setActionHandler('play', () => store.togglePlay()); - navigator.mediaSession.setActionHandler('pause', () => store.togglePlay()); - navigator.mediaSession.setActionHandler('nexttrack', () => store.nextSong()); - navigator.mediaSession.setActionHandler('previoustrack', () => store.previousSong()); - } - } - - async function loadAndPlay(song: Song) { - if (!audio) return; - - state.currentSong = song; - state.currentTime = 0; - state.duration = 0; - state.error = null; - - // NOTE: In the unified app, audio URLs would come from the music backend - // via presigned S3 download URLs. For now, playback requires the backend. - // The store manages queue/state regardless. - try { - // Audio URL would be set here from backend - state.isPlaying = false; - updateMediaSession(song); - } catch (e) { - state.isPlaying = false; - state.error = 'Song konnte nicht abgespielt werden.'; - } - } - - function handleNext() { - const nextIdx = getNextIndex(); - if (nextIdx !== null) { - state.currentIndex = nextIdx; - loadAndPlay(state.queue[nextIdx]); - } else { - state.isPlaying = false; - if (audio) audio.pause(); - } - } - - const store = { - get currentSong() { - return state.currentSong; - }, - get isPlaying() { - return state.isPlaying; - }, - get currentTime() { - return state.currentTime; - }, - get duration() { - return state.duration; - }, - get volume() { - return state.volume; - }, - get repeatMode() { - return state.repeatMode; - }, - get shuffleOn() { - return state.shuffleOn; - }, - get queue() { - return state.queue; - }, - get currentIndex() { - return state.currentIndex; - }, - get showFullPlayer() { - return state.showFullPlayer; - }, - get error() { - return state.error; - }, - - async playSong(song: Song, queue?: Song[], startIndex?: number) { - if (queue) { - state.originalQueue = [...queue]; - state.queue = [...queue]; - state.currentIndex = startIndex ?? 0; - - if (state.shuffleOn) { - state.queue = shuffleArray(state.queue, state.currentIndex); - state.currentIndex = 0; - } - } - await loadAndPlay(song); - }, - - togglePlay() { - if (!audio || !state.currentSong) return; - if (state.isPlaying) { - audio.pause(); - state.isPlaying = false; - } else { - audio.play(); - state.isPlaying = true; - } - }, - - seekTo(time: number) { - if (!audio) return; - audio.currentTime = time; - state.currentTime = time; - }, - - setVolume(vol: number) { - if (!audio) return; - const clamped = Math.max(0, Math.min(1, vol)); - audio.volume = clamped; - state.volume = clamped; - }, - - nextSong() { - handleNext(); - }, - - previousSong() { - if (state.currentTime > 3) { - store.seekTo(0); - return; - } - const prevIdx = getPreviousIndex(); - if (prevIdx !== null) { - state.currentIndex = prevIdx; - loadAndPlay(state.queue[prevIdx]); - } - }, - - toggleShuffle() { - state.shuffleOn = !state.shuffleOn; - if (state.shuffleOn) { - state.queue = shuffleArray(state.queue, state.currentIndex); - state.currentIndex = 0; - } else { - const currentSong = state.queue[state.currentIndex]; - state.queue = [...state.originalQueue]; - const idx = state.queue.findIndex((s) => s.id === currentSong?.id); - state.currentIndex = idx >= 0 ? idx : 0; - } - }, - - toggleRepeat() { - const modes: RepeatMode[] = ['off', 'all', 'one']; - const currentIdx = modes.indexOf(state.repeatMode); - state.repeatMode = modes[(currentIdx + 1) % modes.length]; - }, - - toggleFullPlayer() { - state.showFullPlayer = !state.showFullPlayer; - }, - - clearQueue() { - if (audio) { - audio.pause(); - audio.src = ''; - } - state.currentSong = null; - state.isPlaying = false; - state.currentTime = 0; - state.duration = 0; - state.queue = []; - state.originalQueue = []; - state.currentIndex = 0; - state.showFullPlayer = false; - state.error = null; - }, - - clearError() { - state.error = null; - }, - - getAudioElement(): HTMLAudioElement | null { - return audio; - }, - }; - - return store; -} - -export const playerStore = createPlayerStore(); diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts deleted file mode 100644 index 77d2353b7..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/stores/playlists.svelte.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Playlists Store — Mutations Only - * - * Reads come from liveQuery hooks in queries.ts. - * Handles playlist CRUD and song associations. - */ - -import { db } from '$lib/data/database'; -import { musicPlaylistTable, playlistSongTable } from '../collections'; -import { toPlaylist } from '../queries'; -import { encryptRecord } from '$lib/data/crypto'; -import { MusicEvents } from '@mana/shared-utils/analytics'; -import type { LocalPlaylist, LocalPlaylistSong } from '../types'; - -export const playlistsStore = { - /** Create a new playlist. */ - async create(name: string, description?: string) { - const newLocal: LocalPlaylist = { - id: crypto.randomUUID(), - name, - description: description ?? null, - coverArtPath: null, - }; - // Snapshot the plaintext for the return value before encryptRecord - // mutates `newLocal` in place — UI consumers expect plaintext. - const plaintextSnapshot = toPlaylist({ ...newLocal }); - await encryptRecord('mukkePlaylists', newLocal); - await musicPlaylistTable.add(newLocal); - MusicEvents.playlistCreated(); - return plaintextSnapshot; - }, - - /** Update a playlist. */ - async update(id: string, data: Partial>) { - const diff: Record = { - ...data, - }; - await encryptRecord('mukkePlaylists', diff); - await musicPlaylistTable.update(id, diff); - }, - - /** Soft-delete a playlist and its song associations atomically. */ - async delete(id: string) { - const now = new Date().toISOString(); - // Atomic cascade: playlist + playlistSongs in one Dexie transaction. - await db.transaction('rw', musicPlaylistTable, playlistSongTable, async () => { - await musicPlaylistTable.update(id, { deletedAt: now }); - const allPS = await playlistSongTable.where('playlistId').equals(id).toArray(); - for (const ps of allPS) { - await playlistSongTable.update(ps.id, { deletedAt: now }); - } - }); - MusicEvents.playlistDeleted(); - }, - - /** Add a song to a playlist. */ - async addSong(playlistId: string, songId: string) { - const existing = await playlistSongTable.where('playlistId').equals(playlistId).toArray(); - const maxSort = existing - .filter((ps) => !ps.deletedAt) - .reduce((max, ps) => Math.max(max, ps.sortOrder), -1); - - const newPS: LocalPlaylistSong = { - id: crypto.randomUUID(), - playlistId, - songId, - sortOrder: maxSort + 1, - }; - await playlistSongTable.add(newPS); - }, - - /** Remove a song from a playlist. */ - async removeSong(playlistId: string, songId: string) { - const allPS = await playlistSongTable.where('playlistId').equals(playlistId).toArray(); - const toRemove = allPS.find((ps) => ps.songId === songId && !ps.deletedAt); - if (toRemove) { - const now = new Date().toISOString(); - await playlistSongTable.update(toRemove.id, { deletedAt: now }); - } - }, - - /** Reorder songs in a playlist. */ - async reorderSongs(playlistId: string, songIds: string[]) { - const allPS = await playlistSongTable.where('playlistId').equals(playlistId).toArray(); - const now = new Date().toISOString(); - for (let i = 0; i < songIds.length; i++) { - const ps = allPS.find((p) => p.songId === songIds[i] && !p.deletedAt); - if (ps) { - await playlistSongTable.update(ps.id, { sortOrder: i }); - } - } - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts deleted file mode 100644 index 93c48ee4c..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/stores/projects.svelte.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Projects Store — Mutations Only - * - * Reads come from liveQuery hooks in queries.ts. - * Handles project CRUD. - */ - -import { musicProjectTable } from '../collections'; -import { toProject } from '../queries'; -import { MusicEvents } from '@mana/shared-utils/analytics'; -import type { LocalProject } from '../types'; - -export const projectsStore = { - /** Create a new project. */ - async create(data: { title: string; description?: string; songId?: string }) { - const newLocal: LocalProject = { - id: crypto.randomUUID(), - title: data.title, - description: data.description ?? null, - songId: data.songId ?? null, - }; - await musicProjectTable.add(newLocal); - MusicEvents.projectCreated(); - return toProject(newLocal); - }, - - /** Update a project. */ - async update(id: string, data: Partial>) { - await musicProjectTable.update(id, { - ...data, - }); - }, - - /** Soft-delete a project. */ - async delete(id: string) { - const now = new Date().toISOString(); - await musicProjectTable.update(id, { deletedAt: now }); - MusicEvents.projectDeleted(); - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/music/stores/tags.svelte.ts b/apps/mana/apps/web/src/lib/modules/music/stores/tags.svelte.ts deleted file mode 100644 index 4f074a90a..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/stores/tags.svelte.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Music Tags — Uses shared global tags + module-specific junction table. - */ - -import { db } from '$lib/data/database'; -import { createTagLinkOps } from '@mana/shared-stores'; - -export { - tagMutations, - useAllTags, - getTagById, - getTagsByIds, - getTagColor, -} from '@mana/shared-stores'; - -export const songTagOps = createTagLinkOps({ - table: () => db.table('songTags'), - entityIdField: 'songId', -}); diff --git a/apps/mana/apps/web/src/lib/modules/music/tools.ts b/apps/mana/apps/web/src/lib/modules/music/tools.ts deleted file mode 100644 index c06a22238..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/tools.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ModuleTool } from '$lib/data/tools/types'; - -export const musicTools: ModuleTool[] = [ - { - name: 'create_playlist', - module: 'music', - description: 'Erstellt eine neue Playlist', - parameters: [ - { name: 'name', type: 'string', description: 'Name der Playlist', required: true }, - ], - async execute(params) { - const { playlistsStore } = await import('./stores/playlists.svelte'); - const playlist = await playlistsStore.create(params.name as string); - return { success: true, data: playlist, message: `Playlist "${params.name}" erstellt` }; - }, - }, -]; diff --git a/apps/mana/apps/web/src/lib/modules/music/types.ts b/apps/mana/apps/web/src/lib/modules/music/types.ts deleted file mode 100644 index 0815b459b..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/types.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Music module types for the unified app. - */ - -import type { BaseRecord } from '@mana/local-store'; - -export interface LocalSong extends BaseRecord { - title: string; - artist?: string | null; - album?: string | null; - albumArtist?: string | null; - genre?: string | null; - trackNumber?: number | null; - year?: number | null; - duration?: number | null; - storagePath: string; - coverArtPath?: string | null; - fileSize?: number | null; - bpm?: number | null; - favorite: boolean; - playCount: number; - lastPlayedAt?: string | null; -} - -export interface LocalPlaylist extends BaseRecord { - name: string; - description?: string | null; - coverArtPath?: string | null; -} - -export interface LocalPlaylistSong extends BaseRecord { - playlistId: string; - songId: string; - sortOrder: number; -} - -export interface LocalProject extends BaseRecord { - title: string; - description?: string | null; - songId?: string | null; -} - -export interface LocalMarker extends BaseRecord { - beatId: string; - type: 'verse' | 'hook' | 'bridge' | 'intro' | 'outro' | 'drop' | 'breakdown' | 'custom'; - label?: string | null; - startTime: number; - endTime?: number | null; - color?: string | null; - sortOrder: number; -} - -// ─── View Types ──────────────────────────────────────────── - -export interface Song { - id: string; - title: string; - artist?: string | null; - album?: string | null; - albumArtist?: string | null; - genre?: string | null; - trackNumber?: number | null; - year?: number | null; - duration?: number | null; - storagePath: string; - coverArtPath?: string | null; - fileSize?: number | null; - bpm?: number | null; - favorite: boolean; - playCount: number; - lastPlayedAt?: string | null; - createdAt: string; - updatedAt: string; -} - -export interface Playlist { - id: string; - name: string; - description?: string | null; - coverArtPath?: string | null; - createdAt: string; - updatedAt: string; -} - -export interface Project { - id: string; - title: string; - description?: string | null; - songId?: string | null; - createdAt: string; - updatedAt: string; -} - -export interface Album { - album: string; - albumArtist: string; - year: number | null; - coverArtPath: string | null; - songCount: number; -} - -export interface Artist { - artist: string; - songCount: number; - albumCount: number; -} - -export interface Genre { - genre: string; - songCount: number; -} - -export interface LibraryStats { - totalSongs: number; - totalArtists: number; - totalAlbums: number; - totalGenres: number; - totalDuration: number; - totalPlays: number; -} - -export type RepeatMode = 'off' | 'all' | 'one'; diff --git a/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte deleted file mode 100644 index 6445e570c..000000000 --- a/apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte +++ /dev/null @@ -1,183 +0,0 @@ - - - - - detail.deleteWithUndo({ - label: $_('music.detail.toast_deleted'), - delete: () => libraryStore.delete(songId), - goBack, - })} -> - {#snippet body(song)} -
- - -
- -
-
- {$_('music.detail.prop_artist')} - -
- -
- {$_('music.detail.prop_album')} - -
- -
- {$_('music.detail.prop_genre')} - -
- -
- {$_('music.detail.prop_year')} - -
- -
- {$_('music.detail.prop_bpm')} - -
- -
- {$_('music.detail.prop_duration')} - {formatDuration(song.duration)} -
- -
- {$_('music.detail.prop_play_count')} - {song.playCount} -
-
- -
- {$_('music.detail.meta_created', { - values: { date: formatDate(new Date(song.createdAt ?? '')) }, - })} - {#if song.updatedAt} - {$_('music.detail.meta_updated', { - values: { date: formatDate(new Date(song.updatedAt)) }, - })} - {/if} - {#if song.lastPlayedAt} - {$_('music.detail.meta_last_played', { - values: { date: formatDate(new Date(song.lastPlayedAt)) }, - })} - {/if} -
- {/snippet} -
diff --git a/apps/mana/apps/web/src/lib/search/providers/index.ts b/apps/mana/apps/web/src/lib/search/providers/index.ts index 9b600e682..d4683b1cc 100644 --- a/apps/mana/apps/web/src/lib/search/providers/index.ts +++ b/apps/mana/apps/web/src/lib/search/providers/index.ts @@ -23,7 +23,6 @@ export function registerAllProviders(registry: SearchRegistry): void { // 'cards': dekommissioniert 2026-05-08 — Cards eigenständig auf cardecky.mana.how. registry.registerLazy('picture', () => import('./picture').then((m) => m.pictureSearchProvider)); registry.registerLazy('presi', () => import('./presi').then((m) => m.presiSearchProvider)); - registry.registerLazy('music', () => import('./music').then((m) => m.musicSearchProvider)); registry.registerLazy('quotes', () => import('./quotes').then((m) => m.quotesSearchProvider)); registry.registerLazy('clock', () => import('./clock').then((m) => m.clockSearchProvider)); } diff --git a/apps/mana/apps/web/src/lib/search/providers/music.ts b/apps/mana/apps/web/src/lib/search/providers/music.ts deleted file mode 100644 index bb840beb4..000000000 --- a/apps/mana/apps/web/src/lib/search/providers/music.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { db } from '$lib/data/database'; -import { decryptRecords } from '$lib/data/crypto'; -import { getManaApp } from '@mana/shared-branding'; -import { scoreRecord, truncateSubtitle } from '../scoring'; -import type { SearchProvider, SearchResult, SearchOptions } from '../types'; - -const app = getManaApp('music'); - -export const musicSearchProvider: SearchProvider = { - appId: 'music', - appName: 'Music', - appIcon: app?.icon, - appColor: app?.color, - searchableTypes: ['song', 'playlist', 'project'], - - async search(query: string, options?: SearchOptions): Promise { - const limit = options?.limit ?? 5; - const results: SearchResult[] = []; - - // Search songs. title is encrypted at rest; the scorer needs - // plaintext to do substring matching against the user query. - const rawSongs = await db.table('songs').toArray(); - const visibleSongs = rawSongs.filter((s) => !s.deletedAt); - const songs = await decryptRecords('songs', visibleSongs); - for (const song of songs) { - const { score, matchedField } = scoreRecord( - [ - { name: 'title', value: song.title, weight: 1.0 }, - { name: 'artist', value: song.artist, weight: 0.8 }, - { name: 'album', value: song.album, weight: 0.6 }, - { name: 'genre', value: song.genre, weight: 0.4 }, - ], - query - ); - if (score > 0) { - results.push({ - id: song.id, - type: 'song', - appId: 'music', - title: song.title, - subtitle: [song.artist, song.album].filter(Boolean).join(' · ') || undefined, - appIcon: app?.icon, - appColor: app?.color, - href: '/music/library', - score, - matchedField, - }); - } - } - - // Search playlists (Dexie table name kept for backward compat). - // name + description are encrypted at rest. - const rawPlaylists = await db.table('mukkePlaylists').toArray(); - const visiblePlaylists = rawPlaylists.filter((p) => !p.deletedAt); - const playlists = await decryptRecords('mukkePlaylists', visiblePlaylists); - for (const pl of playlists) { - const { score, matchedField } = scoreRecord( - [ - { name: 'name', value: pl.name, weight: 1.0 }, - { name: 'description', value: pl.description, weight: 0.7 }, - ], - query - ); - if (score > 0) { - results.push({ - id: pl.id, - type: 'playlist', - appId: 'music', - title: pl.name, - subtitle: truncateSubtitle(pl.description) || 'Playlist', - appIcon: app?.icon, - appColor: app?.color, - href: `/music/playlists/${pl.id}`, - score, - matchedField, - }); - } - } - - // Search projects (Dexie table name kept for backward compat) - const projects = await db.table('mukkeProjects').toArray(); - for (const proj of projects) { - if (proj.deletedAt) continue; - const { score, matchedField } = scoreRecord( - [ - { name: 'title', value: proj.title, weight: 1.0 }, - { name: 'description', value: proj.description, weight: 0.7 }, - ], - query - ); - if (score > 0) { - results.push({ - id: proj.id, - type: 'project', - appId: 'music', - title: proj.title, - subtitle: truncateSubtitle(proj.description) || 'Projekt', - appIcon: app?.icon, - appColor: app?.color, - href: `/music/projects`, - score, - matchedField, - }); - } - } - - return results.sort((a, b) => b.score - a.score).slice(0, limit); - }, -}; diff --git a/apps/mana/apps/web/src/lib/splitscreen/registry.ts b/apps/mana/apps/web/src/lib/splitscreen/registry.ts index f5197b15e..a36140392 100644 --- a/apps/mana/apps/web/src/lib/splitscreen/registry.ts +++ b/apps/mana/apps/web/src/lib/splitscreen/registry.ts @@ -14,7 +14,6 @@ const SPLIT_APP_ID_LIST = [ 'picture', 'cards', 'quotes', - 'music', 'storage', 'presi', 'inventory', diff --git a/apps/mana/apps/web/src/lib/stores/dashboard.svelte.ts b/apps/mana/apps/web/src/lib/stores/dashboard.svelte.ts index 66846840f..cab747370 100644 --- a/apps/mana/apps/web/src/lib/stores/dashboard.svelte.ts +++ b/apps/mana/apps/web/src/lib/stores/dashboard.svelte.ts @@ -233,7 +233,6 @@ export const dashboardStore = { 'chat-recent', 'contacts-favorites', 'quotes-quote', - 'music-library', 'presi-decks', ] as WidgetType[] ).filter((type) => { diff --git a/apps/mana/apps/web/src/lib/types/dashboard.test.ts b/apps/mana/apps/web/src/lib/types/dashboard.test.ts index ddcd5de81..965552fe7 100644 --- a/apps/mana/apps/web/src/lib/types/dashboard.test.ts +++ b/apps/mana/apps/web/src/lib/types/dashboard.test.ts @@ -49,7 +49,6 @@ describe('WIDGET_REGISTRY', () => { 'cards', 'times', 'storage', - 'music', 'presi', 'mana-auth', 'period', @@ -75,7 +74,6 @@ describe('WIDGET_REGISTRY', () => { expect(types).toContain('picture-recent'); expect(types).toContain('clock-timers'); expect(types).toContain('storage-usage'); - expect(types).toContain('music-library'); expect(types).toContain('presi-decks'); }); diff --git a/apps/mana/apps/web/src/lib/types/dashboard.ts b/apps/mana/apps/web/src/lib/types/dashboard.ts index b536f21ca..2fc805676 100644 --- a/apps/mana/apps/web/src/lib/types/dashboard.ts +++ b/apps/mana/apps/web/src/lib/types/dashboard.ts @@ -21,7 +21,6 @@ export type WidgetType = | 'picture-recent' // Picture API: recent generations | 'clock-timers' // Clock: active timers and alarms | 'storage-usage' // Storage: file storage stats - | 'music-library' // Music: music library stats | 'presi-decks' // Presi: recent presentations | 'active-timer' // Times: running timer | 'day-timeline' // TimeBlocks: chronological day timeline @@ -125,7 +124,6 @@ export interface WidgetMeta { | 'picture' | 'cards' | 'storage' - | 'music' | 'presi' | 'times' | 'period' @@ -244,15 +242,6 @@ export const WIDGET_REGISTRY: WidgetMeta[] = [ allowMultiple: false, requiredBackend: 'storage', }, - { - type: 'music-library', - nameKey: 'dashboard.widgets.music.title', - descriptionKey: 'dashboard.widgets.music.description', - icon: '🎵', - defaultSize: 'medium', - allowMultiple: false, - requiredBackend: 'music', - }, { type: 'presi-decks', nameKey: 'dashboard.widgets.presi.title', diff --git a/apps/mana/apps/web/src/routes/(app)/music/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/music/+layout.svelte deleted file mode 100644 index 9abbd52d2..000000000 --- a/apps/mana/apps/web/src/routes/(app)/music/+layout.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -{@render children()} diff --git a/apps/mana/apps/web/src/routes/(app)/music/+page.svelte b/apps/mana/apps/web/src/routes/(app)/music/+page.svelte deleted file mode 100644 index 04326dfbb..000000000 --- a/apps/mana/apps/web/src/routes/(app)/music/+page.svelte +++ /dev/null @@ -1,138 +0,0 @@ - - - - Music - Mana - - - -
-

Music

- - -
-

- Bibliothek -

-
-
-

Songs

-

{stats.totalSongs}

-
-
-

Alben

-

{stats.totalAlbums}

-
-
-

Kunstler

-

{stats.totalArtists}

-
-
-

Genres

-

{stats.totalGenres}

-
-
-
- - -
-

- Schnellzugriff -

- -
- - -
-
-

- Letzte Projekte -

- - Alle anzeigen - -
- {#if projectsCtx.value.length === 0} -
- -

Noch keine Projekte

-
- {:else} -
- {#each projectsCtx.value.slice(0, 6) as project (project.id)} -
-

{project.title}

- {#if project.description} -

- {project.description} -

- {/if} -

- Aktualisiert {formatDate(project.updatedAt)} -

-
- {/each} -
- {/if} -
-
-
diff --git a/apps/mana/apps/web/src/routes/(app)/music/library/+page.svelte b/apps/mana/apps/web/src/routes/(app)/music/library/+page.svelte deleted file mode 100644 index a7e141129..000000000 --- a/apps/mana/apps/web/src/routes/(app)/music/library/+page.svelte +++ /dev/null @@ -1,251 +0,0 @@ - - - - Bibliothek - Music - Mana - - - -
- -
- - - -

Bibliothek

-
- - -
- {#each tabs as tab} - - {/each} -
- - - {#if activeTab === 'songs'} -
- - -
- {/if} - - - {#if activeTab === 'songs'} - {#if filteredSongs.length === 0} -
- -

- {searchQuery ? 'Keine Songs gefunden' : 'Noch keine Songs in deiner Bibliothek'} -

-
- {:else} -
- -
- - Titel - Kunstler - Dauer - - -
- - {#each filteredSongs as song, index (song.id)} -
handlePlaySong(song, index)} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - handlePlaySong(song, index); - } - }} - class="group grid grid-cols-[40px_1fr_1fr_80px_40px_40px] items-center gap-4 px-4 py-3 transition-colors hover:bg-[hsl(var(--color-muted))] {playerStore - .currentSong?.id === song.id - ? 'bg-[hsl(var(--color-primary)/0.05)]' - : ''}" - > -
- - {#if playerStore.currentSong?.id === song.id && playerStore.isPlaying} -
- -
- {:else} - - {/if} -
- - {song.title} - - - {song.artist ?? 'Unbekannt'} - - - {formatDuration(song.duration)} - - - -
- {/each} -
- {/if} - {/if} - - - {#if activeTab === 'albums'} - {#if albums.length === 0} -
-

Keine Alben gefunden

-
- {:else} -
- {#each albums as album} -
-
- -
-

- {album.album} -

-

- {album.songCount} - {album.songCount === 1 ? 'Song' : 'Songs'} -

-
- {/each} -
- {/if} - {/if} - - - {#if activeTab === 'genres'} - {#if genres.length === 0} -
-

Keine Genres gefunden

-
- {:else} -
- {#each genres as genre} -
- {genre.genre} - - {genre.songCount} - {genre.songCount === 1 ? 'Song' : 'Songs'} - -
- {/each} -
- {/if} - {/if} -
-
diff --git a/apps/mana/apps/web/src/routes/(app)/music/playlists/+page.svelte b/apps/mana/apps/web/src/routes/(app)/music/playlists/+page.svelte deleted file mode 100644 index 7a30f67ca..000000000 --- a/apps/mana/apps/web/src/routes/(app)/music/playlists/+page.svelte +++ /dev/null @@ -1,182 +0,0 @@ - - - - Playlists - Music - Mana - - - -
- -
-
- - - -

Playlists

-
- -
- - {#if playlistsCtx.value.length === 0} -
- -

Noch keine Playlists

- -
- {:else} - - {/if} -
- - - {#if showCreateModal} -
-
-
-

Neue Playlist

- -
-
{ - e.preventDefault(); - handleCreate(); - }} - > -
- - -
-
- - -
-
- - -
-
-
-
- {/if} -
diff --git a/apps/mana/apps/web/src/routes/(app)/music/playlists/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/music/playlists/[id]/+page.svelte deleted file mode 100644 index bfb3a9176..000000000 --- a/apps/mana/apps/web/src/routes/(app)/music/playlists/[id]/+page.svelte +++ /dev/null @@ -1,230 +0,0 @@ - - - - {playlist?.name || 'Playlist'} - Music - Mana - - - -
- -
-
- - - -
- {#if isEditingName} -
- e.key === 'Enter' && saveName()} - class="rounded border border-[hsl(var(--color-border))] bg-transparent px-2 py-1 text-xl font-bold focus:outline-none focus:ring-1 focus:ring-[hsl(var(--color-primary))]" - /> - - -
- {:else} - - {/if} -

- {songs.length} - {songs.length === 1 ? 'Song' : 'Songs'} -

-
-
-
- {#if songs.length > 0} - - {/if} - - -
-
- - - {#if songs.length === 0} -
- -

Keine Songs in dieser Playlist

-
- {:else} -
- {#each songs as song, index (song.id)} -
handlePlaySong(song, index)} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - handlePlaySong(song, index); - } - }} - class="group flex items-center gap-4 border-b border-[hsl(var(--color-border))] px-4 py-3 transition-colors last:border-b-0 hover:bg-[hsl(var(--color-muted))] {playerStore - .currentSong?.id === song.id - ? 'bg-[hsl(var(--color-primary)/0.05)]' - : ''}" - > -
- - {#if playerStore.currentSong?.id === song.id && playerStore.isPlaying} -
- -
- {:else} - - {/if} -
-
-

- {song.title} -

-

- {song.artist ?? 'Unbekannt'} -

-
- - {formatDuration(song.duration)} - - -
- {/each} -
- {/if} -
- - - (showShare = false)} - url={shareUrl} - title={playlist?.name ?? 'Playlist'} - source="music" - description="{songs.length} {songs.length === 1 ? 'Song' : 'Songs'}" - /> -
diff --git a/apps/mana/apps/web/src/routes/(app)/music/projects/+page.svelte b/apps/mana/apps/web/src/routes/(app)/music/projects/+page.svelte deleted file mode 100644 index cab31a5cd..000000000 --- a/apps/mana/apps/web/src/routes/(app)/music/projects/+page.svelte +++ /dev/null @@ -1,181 +0,0 @@ - - - - Projekte - Music - Mana - - - -
- -
-
- - - -

Projekte

-
- -
- - {#if projectsCtx.value.length === 0} -
- -

Noch keine Projekte

- -
- {:else} -
- {#each projectsCtx.value as project (project.id)} -
-
-

{project.title}

- -
- {#if project.description} -

- {project.description} -

- {/if} -

- Aktualisiert {formatDate(project.updatedAt)} -

-
- {/each} -
- {/if} -
- - - {#if showCreateModal} -
-
-
-

Neues Projekt

- -
-
{ - e.preventDefault(); - handleCreate(); - }} - > -
- - -
-
- - -
-
- - -
-
-
-
- {/if} -
diff --git a/apps/mukke/CLAUDE.md b/apps/mukke/CLAUDE.md deleted file mode 100644 index e9cc22c72..000000000 --- a/apps/mukke/CLAUDE.md +++ /dev/null @@ -1,191 +0,0 @@ -# Mukke - Music Workspace - -Mukke is a web application for managing your music library, playing tracks, and creating synchronized lyrics. It combines a music player with a beat/lyrics editor featuring waveform visualization, BPM detection, timestamp markers, and exports to multiple formats. - -## Architecture - -``` -apps/mukke/ -├── apps/ -│ ├── backend/ # Hono/Bun server (port 3010) -│ ├── web/ # SvelteKit app (port 5180) -│ └── landing/ # Astro marketing page -├── packages/ -│ └── shared/ # Shared types (@mukke/shared) -└── package.json -``` - -## Quick Start - -```bash -# Start with full database setup -pnpm dev:mukke:full - -# Or start components individually -pnpm docker:up # Start PostgreSQL, Redis, MinIO -pnpm --filter @mukke/server dev # Server on port 3010 -pnpm --filter @mukke/web dev # Web on port 5180 -pnpm --filter @mukke/landing dev # Landing page -``` - -## Backend API Endpoints - -### Songs (Library) -- `POST /songs/upload` - Upload song and get presigned URL -- `GET /songs` - List user's songs (with sort/filter) -- `GET /songs/:id` - Get song details -- `PUT /songs/:id` - Update song metadata -- `PUT /songs/:id/favorite` - Toggle favorite -- `PUT /songs/:id/play` - Increment play count -- `DELETE /songs/:id` - Delete song -- `GET /songs/search?q=` - Search songs -- `POST /songs/:id/extract-metadata` - Extract ID3 tags from file into DB (+ cover art to S3) -- `POST /songs/:id/write-tags` - Write DB metadata as ID3 tags back into MP3 file -- `GET /songs/:id/cover-url` - Get presigned URL for cover art - -### Playlists -- `GET /playlists` - List user's playlists -- `POST /playlists` - Create playlist -- `GET /playlists/:id` - Get playlist with songs -- `PUT /playlists/:id` - Update playlist -- `DELETE /playlists/:id` - Delete playlist -- `POST /playlists/:id/songs` - Add song to playlist -- `DELETE /playlists/:id/songs/:songId` - Remove song -- `PUT /playlists/:id/songs/reorder` - Reorder songs - -### Library (Aggregates) -- `GET /library/albums` - Get albums (grouped) -- `GET /library/artists` - Get artists (grouped) -- `GET /library/genres` - Get genres (grouped) -- `GET /library/stats` - Library statistics - -### Projects (Editor) -- `GET /projects` - List user's projects -- `GET /projects/:id` - Get project with beat and lyrics -- `POST /projects` - Create project -- `POST /projects/from-song/:songId` - Create project from library song -- `PUT /projects/:id` - Update project -- `DELETE /projects/:id` - Delete project - -### Beats -- `GET /beats/project/:projectId` - Get beat for project -- `GET /beats/:id` - Get beat with markers -- `GET /beats/:id/download-url` - Get presigned download URL -- `POST /beats/upload` - Create beat and get upload URL -- `PUT /beats/:id/metadata` - Update BPM, duration, waveform data -- `DELETE /beats/:id` - Delete beat - -### Markers -- `GET /markers/beat/:beatId` - Get markers for beat -- `POST /markers` - Create marker -- `POST /markers/bulk` - Bulk create markers -- `PUT /markers/:id` - Update marker -- `PUT /markers/bulk` - Bulk update markers -- `DELETE /markers/:id` - Delete marker - -### Lyrics -- `GET /lyrics/project/:projectId` - Get lyrics with synced lines -- `POST /lyrics/project/:projectId` - Create/update lyrics content -- `POST /lyrics/:id/sync` - Sync line timestamps - -### Export -- `GET /export/:projectId?format=lrc|srt|json` - Export project - -## Database Schema - -```typescript -// songs - Music library -{ id, userId, title, artist, album, albumArtist, genre, trackNumber, year, duration, - storagePath, coverArtPath, fileSize, bpm, favorite, playCount, lastPlayedAt, addedAt, updatedAt } - -// playlists - User playlists -{ id, userId, name, description, coverArtPath, createdAt, updatedAt } - -// playlist_songs - Playlist-Song join table -{ id, playlistId, songId, sortOrder, addedAt } - -// projects - Editor projects -{ id, userId, title, description, songId, createdAt, updatedAt } - -// beats - Audio files for editor -{ id, projectId, storagePath, filename, duration, bpm, bpmConfidence, waveformData } - -// markers - Section markers -{ id, beatId, type, label, startTime, endTime, color, sortOrder } - -// lyrics - Full lyrics text -{ id, projectId, content } - -// lyric_lines - Synced lines -{ id, lyricsId, lineNumber, text, startTime, endTime } -``` - -## Supported Audio Formats - -Playback uses HTML5 Audio (browser-native codec support). Upload accepts any `audio/*` MIME type. - -| Format | Extensions | Browser Playback | Notes | -|--------|-----------|-----------------|-------| -| MP3 | `.mp3` | All browsers | ID3 tag read/write supported | -| WAV | `.wav` | All browsers | Uncompressed PCM | -| OGG Vorbis | `.ogg` | Chrome, Firefox, Edge | No Safari support | -| FLAC | `.flac` | All modern browsers | Lossless | -| AAC/M4A | `.aac`, `.m4a` | All browsers | Common iOS format | -| OPUS | `.opus` | Chrome, Firefox, Edge | Best quality/size ratio | -| WebM | `.webm` | Chrome, Firefox, Edge | Container format | -| AIFF | `.aiff`, `.aif` | Safari, Chrome | Common macOS format | -| WMA | `.wma` | Edge only | Legacy Windows format | -| ALAC | `.alac` | Safari | Apple Lossless | -| APE | `.ape` | None natively | Monkey's Audio (upload/metadata only) | -| WavPack | `.wv` | None natively | Hybrid lossless (upload/metadata only) | -| DSF/DFF | `.dsf`, `.dff` | None natively | DSD audio (upload/metadata only) | - -**Note:** Formats without native browser playback can be uploaded and have metadata extracted (via `music-metadata`), but require server-side transcoding for playback (not yet implemented). - -## Key Technologies - -| Component | Technology | -|-----------|------------| -| Frontend | SvelteKit 2, Svelte 5, Tailwind CSS 4 | -| Waveform | wavesurfer.js 7.x | -| BPM Detection | Web Audio API (peak detection) | -| Metadata | music-metadata (server-side) | -| Backend | Hono + Bun, Drizzle ORM | -| Database | PostgreSQL | -| Storage | MinIO (S3-compatible) | -| Auth | mana-core-auth | - -## Environment Variables - -### Backend (.env) -``` -DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/mukke -MANA_AUTH_URL=http://localhost:3001 -S3_ENDPOINT=http://localhost:9000 -S3_REGION=us-east-1 -S3_ACCESS_KEY=minioadmin -S3_SECRET_KEY=minioadmin -S3_BUCKET=mukke-storage -``` - -### Web (.env) -``` -PUBLIC_MANA_AUTH_URL=http://localhost:3001 -PUBLIC_BACKEND_URL=http://localhost:3010 -``` - -## Development Commands - -```bash -# Database -pnpm --filter @mukke/server db:push # Push schema -pnpm --filter @mukke/server db:studio # Open Drizzle Studio - -# Type checking -pnpm --filter @mukke/server type-check -pnpm --filter @mukke/web type-check - -# Build -pnpm --filter @mukke/server build -pnpm --filter @mukke/web build -``` diff --git a/apps/mukke/apps/landing/astro.config.mjs b/apps/mukke/apps/landing/astro.config.mjs deleted file mode 100644 index fe89fd4b2..000000000 --- a/apps/mukke/apps/landing/astro.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'astro/config'; -import sitemap from '@astrojs/sitemap'; - -export default defineConfig({ - site: 'https://mukke.app', - integrations: [sitemap()], -}); diff --git a/apps/mukke/apps/landing/package.json b/apps/mukke/apps/landing/package.json deleted file mode 100644 index a08121e62..000000000 --- a/apps/mukke/apps/landing/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@mukke/landing", - "type": "module", - "version": "0.2.0", - "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro check && astro build", - "preview": "astro preview", - "astro": "astro", - "type-check": "astro check" - }, - "dependencies": { - "@astrojs/check": "^0.9.4", - "@astrojs/sitemap": "^3.3.0", - "@mana/shared-landing-ui": "workspace:*", - "astro": "^5.1.1", - "typescript": "^5.7.2" - } -} diff --git a/apps/mukke/apps/landing/src/layouts/Layout.astro b/apps/mukke/apps/landing/src/layouts/Layout.astro deleted file mode 100644 index 6232f8b7c..000000000 --- a/apps/mukke/apps/landing/src/layouts/Layout.astro +++ /dev/null @@ -1,61 +0,0 @@ ---- -import Analytics from '@mana/shared-landing-ui/atoms/Analytics.astro'; - -interface Props { - title: string; -} - -const { title } = Astro.props; ---- - - - - - - - - - - - { - import.meta.env.PUBLIC_UMAMI_WEBSITE_ID && ( - - - -``` - -**Pro:** Volle Svelte-Reaktivität, Tree-Shakeable, einfach zu warten -**Contra:** Nicht dynamisch ladbar für User-Visualisierungen - -### Option B: Canvas 2D Render Functions (Custom) - -User-Visualisierungen als reine Render-Funktionen: - -```typescript -interface CustomVisualizerFn { - setup?: (ctx: CanvasRenderingContext2D, width: number, height: number) => void; - render: (ctx: CanvasRenderingContext2D, data: AudioData, config: VisualizerConfig, - width: number, height: number) => void; - destroy?: () => void; -} -``` - -**Pro:** Einfach, sicher (nur Canvas-Zugriff), leicht per Code-Editor erstellbar -**Contra:** Nur 2D, kein DOM-Zugriff - -### Option C: WebGL/Shader (Advanced) - -GLSL Fragment Shaders für GPU-beschleunigte Visualisierungen: - -```glsl -// User schreibt nur den Fragment Shader -uniform float u_time; -uniform float u_bass; -uniform float u_mid; -uniform float u_high; -uniform float u_volume; -uniform sampler2D u_frequency; // Frequenzdaten als Textur - -void main() { - vec2 uv = gl_FragCoord.xy / u_resolution; - // ... Shader-Code - gl_FragColor = vec4(color, 1.0); -} -``` - -**Pro:** GPU-beschleunigt, visuell beeindruckend, Shadertoy-kompatibel -**Contra:** Steile Lernkurve, schwer zu debuggen - -### Empfehlung: Hybrid-Ansatz - -| Typ | Technologie | Verwendung | -|-----|------------|------------| -| Built-in | Svelte + Canvas 2D | Alle mitgelieferten Visualisierungen | -| Custom Canvas | Sandboxed Canvas 2D Function | User-erstellte 2D-Visualisierungen | -| Custom Shader | WebGL Fragment Shader | User-erstellte GPU-Visualisierungen | - ---- - -## 5. Custom Visualizer System - -### User-Workflow - -1. **Galerie öffnen** → Alle verfügbaren Visualisierungen als Grid mit Live-Preview -2. **"Create New"** → Code-Editor öffnet sich -3. **Template wählen** → Startercode für Canvas 2D oder Shader -4. **Code schreiben** → Live-Preview neben dem Editor -5. **KI-Assistent** → "Erstelle eine Visualisierung die..." → Code wird generiert -6. **Speichern** → In der persönlichen Bibliothek -7. **Teilen** → Als Community Preset veröffentlichen (optional) - -### Code-Editor Integration - -``` -┌─────────────────────────────────────────────────────┐ -│ ┌─────────────────────┐ ┌──────────────────────┐ │ -│ │ │ │ │ │ -│ │ Code Editor │ │ Live Preview │ │ -│ │ (Monaco/CM6) │ │ (Canvas) │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ └─────────────────────┘ └──────────────────────┘ │ -│ ┌──────────────────────────────────────────────────┐│ -│ │ Config Panel: [barCount: 32] [color: #ff0] ││ -│ └──────────────────────────────────────────────────┘│ -│ [💾 Save] [▶ Test with Audio] [🤖 Ask AI] [📤 Share]│ -└─────────────────────────────────────────────────────┘ -``` - -### Sandboxing (Sicherheit) - -Custom Code läuft **nicht** direkt im Hauptthread: - -``` -Option 1: new Function() mit Whitelist - - Kein Zugriff auf window, document, fetch, etc. - - Nur ctx (Canvas), data (AudioData), config erreichbar - - Einfach, performant, leichte Einschränkungen - -Option 2: Web Worker + OffscreenCanvas - - Code läuft in isoliertem Worker - - Rendert auf OffscreenCanvas, wird in Hauptthread übertragen - - Sicherer, aber komplexer und nicht alle Browser unterstützen OffscreenCanvas - -Option 3: iframe Sandbox - - Maximale Isolation - - Overhead durch postMessage-Kommunikation - - Overkill für Canvas-Rendering -``` - -**Empfehlung:** Option 1 (`new Function()`) für Canvas 2D, direkte WebGL-Ausführung für Shaders (Shader-Code ist von Natur aus sandboxed auf der GPU). - -### Datenbank-Schema - -```typescript -// Erweiterung der DB (Backend) -visualizers: { - id: uuid, - userId: uuid, - meta: jsonb, // VisualizerMeta - config: jsonb, // Default VisualizerConfig - configSchema: jsonb, // JSON Schema für Config-UI - code: text, // Render-Function oder Shader-Code - type: enum('canvas-2d', 'shader'), - isPublic: boolean, - likes: integer, - createdAt: timestamp, - updatedAt: timestamp, -} -``` - ---- - -## 6. Visualizer Galerie UI - -``` -┌─────────────────────────────────────────────────────────┐ -│ 🎵 Visualisierungen [+ Create New] │ -│ │ -│ [All] [Spectrum] [Waveform] [Particles] [Community] │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ ▓▓▓▓▓▓▓▓ │ │ ◉ ))) ) │ │ · · · · │ │ -│ │ ▓▓▓▓▓▓▓▓ │ │ ◉ )))) ) │ │· · · · │ │ -│ │ Freq Bars │ │ Circular │ │ Particles │ │ -│ │ ★ Built-in│ │ ★ Built-in│ │ ★ Built-in│ │ -│ └──────────┘ └──────────┘ └──────────┘ │ -│ │ -│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ -│ │ ~~~~~~~~ │ │ ╱╲╱╲╱╲╱╲ │ │ ▒▒▒▒▒▒▒▒ │ │ -│ │ ~~~~~~~~ │ │ ╱╲╱╲╱╲╱╲ │ │ ▒▒▒▒▒▒▒▒ │ │ -│ │ Waveform │ │ Kaleido │ │ My Custom │ │ -│ │ ★ Built-in│ │ ★ Built-in│ │ 👤 You │ │ -│ └──────────┘ └──────────┘ └──────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -### Config Panel (pro Visualisierung) - -Jede Visualisierung definiert ein `configSchema`, aus dem automatisch UI generiert wird: - -```typescript -const configSchema: ConfigSchema = { - barCount: { type: 'range', min: 8, max: 128, step: 1, label: 'Bar Count' }, - color: { type: 'color', label: 'Color' }, - mirror: { type: 'toggle', label: 'Mirror' }, - sensitivity: { type: 'range', min: 0, max: 2, step: 0.1, label: 'Sensitivity' }, - colorMode: { type: 'select', options: ['gradient', 'solid', 'rainbow'], label: 'Color Mode' }, -}; -``` - ---- - -## 7. KI-Integration - -### Prompt-basierte Erstellung - -User beschreibt die gewünschte Visualisierung in natürlicher Sprache: - -> "Erstelle einen Visualizer der wie Nordlichter aussieht. -> Die Farben sollen zwischen Grün und Lila wechseln, basierend auf den Bässen." - -→ KI generiert Canvas 2D oder Shader Code -→ Live-Preview zeigt das Ergebnis sofort -→ User kann iterieren ("mach die Bewegung schneller", "füge Sterne hinzu") - -### System Prompt für KI - -``` -Du bist ein Musik-Visualisierungs-Experte. Generiere eine render()-Funktion: - -function render(ctx, data, config, width, height) { - // ctx: CanvasRenderingContext2D - // data: { frequency, timeDomain, volume, bass, mid, high, beat, currentTime, bpm } - // config: User-konfigurierbare Werte - // width, height: Canvas-Dimensionen -} - -Regeln: -- Lösche den Canvas mit ctx.clearRect(0, 0, width, height) -- Nutze data.frequency (Uint8Array, 0-255) für Spektrumdaten -- data.bass/mid/high sind normalisiert (0-1) -- data.beat ist true wenn ein Beat erkannt wurde -- Halte den Code performant (60fps) -- Gib auch ein configSchema-Objekt zurück -``` - -### Integration über mana-llm Service - -``` -User Input → mana-llm → Code generiert → Sandbox → Live Preview -``` - -Nutzt den bestehenden `services/mana-llm` als LLM-Abstraktionsschicht. - ---- - -## 8. Fullscreen Visualizer Mode - -Neuer Fullscreen-Modus für immersive Erfahrung: - -- Visualisierung füllt den gesamten Bildschirm -- Minimale Transport-Controls (transparent overlay, auto-hide) -- Song-Info eingeblendet bei Track-Wechsel (fade in/out) -- Keyboard-Shortcuts (Space = Play/Pause, Esc = Exit, N = Next Viz) -- Screensaver-Modus: Wechselt automatisch zwischen Visualisierungen - ---- - -## 9. Datei-Struktur (geplant) - -``` -src/lib/visualizer/ -├── analyzer.ts # Audio Analyzer (existiert) -├── audio-data.ts # AudioData Interface + Berechnung -├── types.ts # Alle Type-Definitionen -├── registry.svelte.ts # Visualizer Registry Store -├── sandbox.ts # Custom Code Sandboxing -├── FrequencyBars.svelte # Built-in (existiert) -├── renderers/ -│ ├── CircularSpectrum.svelte # Kreisförmiges Spektrum -│ ├── ParticleFlow.svelte # Partikel-System -│ ├── Waveform3D.svelte # 3D Waveform -│ ├── ReactiveBlob.svelte # Organische Form -│ ├── CircularBars.svelte # Bars im Kreis -│ ├── SpectrumWave.svelte # Glatte Frequenzkurve -│ ├── StereoField.svelte # Stereo-Analyse -│ └── Kaleidoscope.svelte # Kaleidoskop-Muster -├── components/ -│ ├── VisualizerRenderer.svelte # Router: wählt aktiven Renderer -│ ├── VisualizerGallery.svelte # Galerie-Ansicht -│ ├── VisualizerConfig.svelte # Auto-generiertes Config-Panel -│ ├── VisualizerEditor.svelte # Code-Editor für Custom Viz -│ ├── VisualizerPreview.svelte # Live-Preview Canvas -│ └── FullscreenVisualizer.svelte# Fullscreen-Modus -└── templates/ - ├── canvas-2d-starter.ts # Template für Canvas 2D - └── shader-starter.glsl # Template für GLSL Shader -``` - ---- - -## 10. Implementierungs-Reihenfolge - -| Phase | Was | Aufwand | -|-------|-----|---------| -| **1** ✅ | Frequency Bars + Analyzer (done) | — | -| **2** | AudioData erweitern (bass/mid/high/beat), Registry Store, VisualizerRenderer | S | -| **3** | 3-4 weitere Built-in Renderer (Circular, Particles, Blob, Wave) | M | -| **4** | Galerie UI + Config Panel + Fullscreen Mode | M | -| **5** | Custom Visualizer: Sandbox + Code-Editor + Templates | L | -| **6** | KI-Integration (mana-llm) für Code-Generierung | M | -| **7** | Community: Teilen, Likes, öffentliche Galerie | L | -| **8** | Backend: visualizers-Tabelle, CRUD API, User-Presets | M | - ---- - -## 11. Technologie-Entscheidungen - -| Bereich | Entscheidung | Begründung | -|---------|-------------|------------| -| Built-in Rendering | Canvas 2D | Performant, einfach, kein Dep overhead | -| GPU-Effekte | Raw WebGL (kein Three.js) | Vermeidet ~150kb Dependency für Shader-only | -| Code Editor | CodeMirror 6 | Leichtgewichtig, Svelte-freundlich, besser als Monaco für embedded | -| Sandboxing | `new Function()` mit Whitelist | Guter Kompromiss aus Sicherheit und Performance | -| State | Svelte 5 Runes Store | Konsistent mit Rest der App | -| Persistenz | JSONB in PostgreSQL | Flexibel, keine Migrationen bei Config-Änderungen | -| KI | mana-llm Service | Bereits vorhanden, LLM-agnostisch | diff --git a/apps/mukke/package.json b/apps/mukke/package.json deleted file mode 100644 index 9dae0cf65..000000000 --- a/apps/mukke/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "mukke", - "version": "0.2.0", - "private": true, - "scripts": { - "dev": "pnpm run --filter=@mukke/* --parallel dev" - } -} diff --git a/apps/mukke/packages/shared/package.json b/apps/mukke/packages/shared/package.json deleted file mode 100644 index 544ce4ddc..000000000 --- a/apps/mukke/packages/shared/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@mukke/shared", - "version": "0.2.0", - "private": true, - "type": "module", - "main": "./src/index.ts", - "exports": { - ".": "./src/index.ts", - "./types": "./src/types/index.ts" - }, - "scripts": { - "type-check": "tsc --noEmit" - }, - "devDependencies": { - "typescript": "^5.7.3" - } -} diff --git a/apps/mukke/packages/shared/src/index.ts b/apps/mukke/packages/shared/src/index.ts deleted file mode 100644 index fcb073fef..000000000 --- a/apps/mukke/packages/shared/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './types'; diff --git a/apps/mukke/packages/shared/src/types/beat.ts b/apps/mukke/packages/shared/src/types/beat.ts deleted file mode 100644 index 153f95185..000000000 --- a/apps/mukke/packages/shared/src/types/beat.ts +++ /dev/null @@ -1,40 +0,0 @@ -export type TranscriptionStatus = 'none' | 'pending' | 'completed' | 'failed'; - -export interface Beat { - id: string; - projectId: string; - storagePath: string; - filename?: string | null; - duration?: number | null; - bpm?: number | null; - bpmConfidence?: number | null; - waveformData?: WaveformData | null; - // STT Transcription fields - transcriptionStatus?: TranscriptionStatus | null; - transcriptionError?: string | null; - transcribedAt?: Date | null; - createdAt: Date; -} - -export interface WaveformData { - peaks: number[]; - sampleRate: number; - duration: number; -} - -export interface CreateBeatDto { - projectId: string; - filename: string; -} - -export interface UpdateBeatDto { - bpm?: number; - bpmConfidence?: number; - duration?: number; - waveformData?: WaveformData; -} - -export interface BeatUploadResponse { - beat: Beat; - uploadUrl: string; -} diff --git a/apps/mukke/packages/shared/src/types/export.ts b/apps/mukke/packages/shared/src/types/export.ts deleted file mode 100644 index c01226899..000000000 --- a/apps/mukke/packages/shared/src/types/export.ts +++ /dev/null @@ -1,57 +0,0 @@ -export type ExportFormat = 'lrc' | 'srt' | 'json' | 'video'; - -export interface ExportOptions { - format: ExportFormat; - includeMarkers?: boolean; - videoOptions?: VideoExportOptions; -} - -export interface VideoExportOptions { - width: number; - height: number; - fps: number; - backgroundColor: string; - textColor: string; - highlightColor: string; - fontFamily: string; - fontSize: number; -} - -export interface LrcExportResult { - content: string; - filename: string; -} - -export interface SrtExportResult { - content: string; - filename: string; -} - -export interface JsonExportResult { - data: JsonExportData; - filename: string; -} - -export interface JsonExportData { - project: { - id: string; - title: string; - description?: string; - }; - beat: { - bpm?: number; - duration?: number; - }; - markers: Array<{ - type: string; - label?: string; - startTime: number; - endTime?: number; - }>; - lyrics: Array<{ - lineNumber: number; - text: string; - startTime?: number; - endTime?: number; - }>; -} diff --git a/apps/mukke/packages/shared/src/types/index.ts b/apps/mukke/packages/shared/src/types/index.ts deleted file mode 100644 index 4f43f88ce..000000000 --- a/apps/mukke/packages/shared/src/types/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './project'; -export * from './beat'; -export * from './marker'; -export * from './lyrics'; -export * from './export'; -export * from './song'; -export * from './playlist'; diff --git a/apps/mukke/packages/shared/src/types/lyrics.ts b/apps/mukke/packages/shared/src/types/lyrics.ts deleted file mode 100644 index 9bb1ee1a2..000000000 --- a/apps/mukke/packages/shared/src/types/lyrics.ts +++ /dev/null @@ -1,55 +0,0 @@ -export interface Lyrics { - id: string; - projectId: string; - content?: string | null; -} - -export interface LyricLine { - id: string; - lyricsId: string; - lineNumber: number; - text: string; - startTime?: number | null; - endTime?: number | null; -} - -export interface CreateLyricsDto { - projectId: string; - content?: string; -} - -export interface UpdateLyricsDto { - content?: string; -} - -export interface CreateLyricLineDto { - lyricsId: string; - lineNumber: number; - text: string; - startTime?: number; - endTime?: number; -} - -export interface UpdateLyricLineDto { - text?: string; - startTime?: number; - endTime?: number; -} - -export interface SyncedLyrics { - lines: SyncedLine[]; -} - -export interface SyncedLine { - lineNumber: number; - text: string; - startTime: number; - endTime?: number; - words?: SyncedWord[]; -} - -export interface SyncedWord { - word: string; - startTime: number; - endTime: number; -} diff --git a/apps/mukke/packages/shared/src/types/marker.ts b/apps/mukke/packages/shared/src/types/marker.ts deleted file mode 100644 index 54e3bb17c..000000000 --- a/apps/mukke/packages/shared/src/types/marker.ts +++ /dev/null @@ -1,49 +0,0 @@ -export type MarkerType = - | 'verse' - | 'hook' - | 'bridge' - | 'intro' - | 'outro' - | 'drop' - | 'breakdown' - | 'custom'; - -export interface Marker { - id: string; - beatId: string; - type: MarkerType; - label?: string | null; - startTime: number; - endTime?: number | null; - color?: string | null; - sortOrder?: number | null; -} - -export interface CreateMarkerDto { - beatId: string; - type: MarkerType; - label?: string; - startTime: number; - endTime?: number; - color?: string; -} - -export interface UpdateMarkerDto { - type?: MarkerType; - label?: string; - startTime?: number; - endTime?: number; - color?: string; - sortOrder?: number; -} - -export const MARKER_COLORS: Record = { - verse: '#3B82F6', // blue - hook: '#EF4444', // red - bridge: '#8B5CF6', // purple - intro: '#22C55E', // green - outro: '#F97316', // orange - drop: '#EC4899', // pink - breakdown: '#14B8A6', // teal - custom: '#6B7280', // gray -}; diff --git a/apps/mukke/packages/shared/src/types/playlist.ts b/apps/mukke/packages/shared/src/types/playlist.ts deleted file mode 100644 index b76984729..000000000 --- a/apps/mukke/packages/shared/src/types/playlist.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Song } from './song'; - -export interface Playlist { - id: string; - userId: string; - name: string; - description: string | null; - coverArtPath: string | null; - createdAt: string; - updatedAt: string; -} - -export interface PlaylistWithSongs extends Playlist { - songs: Song[]; -} - -export interface CreatePlaylistDto { - name: string; - description?: string; -} - -export interface UpdatePlaylistDto { - name?: string; - description?: string; -} diff --git a/apps/mukke/packages/shared/src/types/project.ts b/apps/mukke/packages/shared/src/types/project.ts deleted file mode 100644 index aaac4e85b..000000000 --- a/apps/mukke/packages/shared/src/types/project.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface Project { - id: string; - userId: string; - title: string; - description?: string | null; - createdAt: Date; - updatedAt: Date; -} - -export interface CreateProjectDto { - title: string; - description?: string; -} - -export interface UpdateProjectDto { - title?: string; - description?: string; -} diff --git a/apps/mukke/packages/shared/src/types/song.ts b/apps/mukke/packages/shared/src/types/song.ts deleted file mode 100644 index ab39ed706..000000000 --- a/apps/mukke/packages/shared/src/types/song.ts +++ /dev/null @@ -1,77 +0,0 @@ -export interface Song { - id: string; - userId: string; - title: string; - artist: string | null; - album: string | null; - albumArtist: string | null; - genre: string | null; - trackNumber: number | null; - year: number | null; - month: number | null; - day: number | null; - duration: number | null; - storagePath: string; - coverArtPath: string | null; - fileSize: number | null; - bpm: number | null; - favorite: boolean; - playCount: number; - lastPlayedAt: string | null; - addedAt: string; - updatedAt: string; -} - -export interface Album { - album: string; - albumArtist: string | null; - year: number | null; - coverArtPath: string | null; - songCount: number; -} - -export interface Artist { - artist: string; - songCount: number; - albumCount: number; -} - -export interface Genre { - genre: string; - songCount: number; -} - -export interface LibraryStats { - totalSongs: number; - totalArtists: number; - totalAlbums: number; - totalGenres: number; - totalDuration: number | null; - totalPlays: number; -} - -export type SortField = 'title' | 'artist' | 'album' | 'addedAt' | 'playCount'; -export type SortDirection = 'asc' | 'desc'; - -export interface CreateSongDto { - title: string; - artist?: string; - album?: string; - albumArtist?: string; - genre?: string; - trackNumber?: number; - year?: number; - month?: number; - day?: number; - bpm?: number; -} - -export interface UpdateSongDto extends Partial { - duration?: number; - fileSize?: number; -} - -export interface SongUploadResponse { - song: Song; - uploadUrl: string; -} diff --git a/apps/mukke/packages/shared/tsconfig.json b/apps/mukke/packages/shared/tsconfig.json deleted file mode 100644 index 9976a4fcb..000000000 --- a/apps/mukke/packages/shared/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "noEmit": true - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index 5128757f8..4ca11e1ca 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -447,23 +447,6 @@ export const MANA_APPS: ManaApp[] = [ status: 'beta', requiredTier: 'guest', }, - { - id: 'music', - name: 'Music', - description: { - de: 'Musikproduktion', - en: 'Music Production', - }, - longDescription: { - de: 'Erstelle und verwalte Songs, Playlists und Musikprojekte mit Markern und Arrangements.', - en: 'Create and manage songs, playlists, and music projects with markers and arrangements.', - }, - icon: APP_ICONS.music, - color: '#ec4899', - comingSoon: false, - status: 'beta', - requiredTier: 'guest', - }, { id: 'photos', name: 'Photos',