From 12ba2cf824a7ecafa336c7de86a63e602b52d67b Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Fri, 12 Dec 2025 02:34:43 +0100 Subject: [PATCH] refactor(theme): remove custom theme editor and community themes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unused custom theme functionality: - Delete custom-themes-store.svelte.ts from shared-theme - Remove ThemeEditor, ColorPicker, ThemeLivePreview components - Remove CommunityThemeGallery, ThemeCommunityCard components - Remove ThemeEditorPage, CommunityThemesPage - Simplify ThemePage to show only built-in themes - Remove editor and community routes from contacts app - Update THEMING.md documentation The built-in theme variants (default, ocean, forest, sunset, etc.) provide sufficient customization. Custom theme creation was never fully implemented and added unnecessary complexity. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../src/lib/stores/custom-themes.svelte.ts | 15 - .../web/src/routes/(app)/themes/+page.svelte | 6 - .../(app)/themes/community/+page.svelte | 29 - .../routes/(app)/themes/editor/+page.svelte | 75 --- docs/central-services/THEMING.md | 99 +--- .../community/CommunityThemeGallery.svelte | 266 --------- .../community/ThemeCommunityCard.svelte | 197 ------- .../src/components/editor/ColorPicker.svelte | 349 ----------- .../src/components/editor/ThemeEditor.svelte | 542 ------------------ .../components/editor/ThemeLivePreview.svelte | 485 ---------------- packages/shared-theme-ui/src/index.ts | 11 - .../src/pages/CommunityThemesPage.svelte | 312 ---------- .../src/pages/ThemeEditorPage.svelte | 276 --------- .../src/pages/ThemePage.svelte | 251 +------- .../src/custom-themes-store.svelte.ts | 506 ---------------- packages/shared-theme/src/index.ts | 18 - packages/shared-theme/src/types.ts | 2 +- 17 files changed, 44 insertions(+), 3395 deletions(-) delete mode 100644 apps/contacts/apps/web/src/lib/stores/custom-themes.svelte.ts delete mode 100644 apps/contacts/apps/web/src/routes/(app)/themes/community/+page.svelte delete mode 100644 apps/contacts/apps/web/src/routes/(app)/themes/editor/+page.svelte delete mode 100644 packages/shared-theme-ui/src/components/community/CommunityThemeGallery.svelte delete mode 100644 packages/shared-theme-ui/src/components/community/ThemeCommunityCard.svelte delete mode 100644 packages/shared-theme-ui/src/components/editor/ColorPicker.svelte delete mode 100644 packages/shared-theme-ui/src/components/editor/ThemeEditor.svelte delete mode 100644 packages/shared-theme-ui/src/components/editor/ThemeLivePreview.svelte delete mode 100644 packages/shared-theme-ui/src/pages/CommunityThemesPage.svelte delete mode 100644 packages/shared-theme-ui/src/pages/ThemeEditorPage.svelte delete mode 100644 packages/shared-theme/src/custom-themes-store.svelte.ts diff --git a/apps/contacts/apps/web/src/lib/stores/custom-themes.svelte.ts b/apps/contacts/apps/web/src/lib/stores/custom-themes.svelte.ts deleted file mode 100644 index 1103c1cd1..000000000 --- a/apps/contacts/apps/web/src/lib/stores/custom-themes.svelte.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Custom Themes Store - Manages user's custom themes and community themes - */ - -import { createCustomThemesStore } from '@manacore/shared-theme'; -import { authStore } from './auth.svelte'; - -// Auth URL for theme API calls -const MANA_AUTH_URL = 'http://localhost:3001'; - -// Create the custom themes store -export const customThemesStore = createCustomThemesStore({ - authUrl: MANA_AUTH_URL, - getAccessToken: () => authStore.getAccessToken(), -}); diff --git a/apps/contacts/apps/web/src/routes/(app)/themes/+page.svelte b/apps/contacts/apps/web/src/routes/(app)/themes/+page.svelte index 68bc36719..83ac0006b 100644 --- a/apps/contacts/apps/web/src/routes/(app)/themes/+page.svelte +++ b/apps/contacts/apps/web/src/routes/(app)/themes/+page.svelte @@ -2,7 +2,6 @@ import { goto } from '$app/navigation'; import { ThemePage } from '@manacore/shared-theme-ui'; import { theme } from '$lib/stores/theme'; - import { customThemesStore } from '$lib/stores/custom-themes.svelte'; @@ -17,9 +16,4 @@ onModeChange={(m) => theme.setMode(m)} showBackButton={true} onBack={() => goto('/')} - showCustomThemes={true} - {customThemesStore} - onCreateTheme={() => goto('/themes/editor')} - onEditTheme={(t) => goto(`/themes/editor?id=${t.id}`)} - onCommunityThemes={() => goto('/themes/community')} /> diff --git a/apps/contacts/apps/web/src/routes/(app)/themes/community/+page.svelte b/apps/contacts/apps/web/src/routes/(app)/themes/community/+page.svelte deleted file mode 100644 index 7f4582f95..000000000 --- a/apps/contacts/apps/web/src/routes/(app)/themes/community/+page.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - Community Themes | Contacts - - - goto('/themes')} - onSelectTheme={(t) => { - // Could open a detail modal here - console.log('Selected theme:', t); - }} -/> diff --git a/apps/contacts/apps/web/src/routes/(app)/themes/editor/+page.svelte b/apps/contacts/apps/web/src/routes/(app)/themes/editor/+page.svelte deleted file mode 100644 index 83fb8653b..000000000 --- a/apps/contacts/apps/web/src/routes/(app)/themes/editor/+page.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - - - {themeId ? 'Theme bearbeiten' : 'Neues Theme'} | Contacts - - - goto('/themes')} - onSave={handleSave} - onPublish={handlePublish} -/> diff --git a/docs/central-services/THEMING.md b/docs/central-services/THEMING.md index 53ef158f0..bdebafae5 100644 --- a/docs/central-services/THEMING.md +++ b/docs/central-services/THEMING.md @@ -1,6 +1,6 @@ # Central Theming System -Das zentrale Theming-System ermΓΆglicht einheitliches Aussehen und Benutzereinstellungen ΓΌber alle Manacore-Apps hinweg. Es besteht aus mehreren Schichten: Theme-Varianten, Light/Dark-Modus, Accessibility-Einstellungen und Custom Themes. +Das zentrale Theming-System ermΓΆglicht einheitliches Aussehen und Benutzereinstellungen ΓΌber alle Manacore-Apps hinweg. Es besteht aus mehreren Schichten: Theme-Varianten, Light/Dark-Modus und Accessibility-Einstellungen. ## Architektur @@ -14,11 +14,6 @@ Das zentrale Theming-System ermΓΆglicht einheitliches Aussehen und Benutzereinst β”‚ β”‚ - locale: "de" β”‚ β”‚ β”‚ β”‚ - general: { startPages, sounds, etc. } β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ custom_themes Tabelle (Community Themes) β”‚ β”‚ -β”‚ β”‚ - lightColors, darkColors, author, downloads, etc. β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” @@ -45,18 +40,18 @@ Es gibt 8 vordefinierte Theme-Varianten: ### Standard-Varianten (PillNav) | Name | Farbe | Icon | Hue | |------|-------|------|-----| -| `lume` | Gold ✨ | sparkle | 47 | -| `nature` | GrΓΌn 🌿 | leaf | 122 | -| `stone` | Blau-Grau πŸͺ¨ | hexagon | 200 | -| `ocean` | Blau 🌊 | waves | 199 | +| `lume` | Gold | sparkle | 47 | +| `nature` | GrΓΌn | leaf | 122 | +| `stone` | Blau-Grau | hexagon | 200 | +| `ocean` | Blau | waves | 199 | ### Erweiterte Varianten (Themes-Seite) | Name | Farbe | Icon | Hue | |------|-------|------|-----| -| `sunset` | Coral/Orange πŸŒ… | sun | 15 | -| `midnight` | Violett πŸŒ™ | moon | 260 | -| `rose` | Pink 🌹 | flower | 340 | -| `lavender` | Lavendel πŸ’œ | sparkle | 270 | +| `sunset` | Coral/Orange | sun | 15 | +| `midnight` | Violett | moon | 260 | +| `rose` | Pink | flower | 340 | +| `lavender` | Lavendel | sparkle | 270 | ## Theme-Modus @@ -223,55 +218,6 @@ a11y.resetAll(); - Respektiert `prefers-reduced-motion` - Kann manuell ΓΌberschrieben werden -## Custom Themes - -### Custom Themes Store - -```typescript -import { createCustomThemesStore } from '@manacore/shared-theme'; - -export const customThemes = createCustomThemesStore({ - authUrl: 'http://localhost:3001', - getAccessToken: () => authStore.getAccessToken(), -}); - -// Eigene Themes laden -await customThemes.loadCustomThemes(); - -// Theme erstellen -const newTheme = await customThemes.createTheme({ - name: 'Mein Theme', - emoji: '🎨', - lightColors: { primary: '200 80% 50%', ... }, - darkColors: { primary: '200 70% 60%', ... }, -}); - -// Community Themes durchsuchen -await customThemes.browseCommunity({ - sort: 'popular', - search: 'dark', -}); - -// Theme herunterladen -await customThemes.downloadTheme(themeId); - -// Theme anwenden -customThemes.applyCustomTheme(theme); -``` - -### Theme Editor - -Der Theme Editor erlaubt das visuelle Erstellen von Themes: - -**Hauptfarben (immer sichtbar):** -- Primary, Background, Surface, Foreground -- Error, Success, Warning - -**Erweiterte Farben (zugeklappt):** -- PrimaryForeground, Secondary, SecondaryForeground -- SurfaceHover, SurfaceElevated, Muted, MutedForeground -- Border, BorderStrong, Input, Ring - ## UI-Komponenten ### ThemePage @@ -285,10 +231,13 @@ VollstΓ€ndige Themes-Seite mit allen Optionen: theme.setVariant(v)} + showModeSelector={true} + currentMode={theme.mode} + onModeChange={(m) => theme.setMode(m)} a11yStore={a11y} - showAccessibility={true} - showPinnedThemes={true} + showA11ySettings={true} /> ``` @@ -406,7 +355,6 @@ theme: { | `src/a11y-constants.ts` | A11y Konstanten | | `src/a11y-utils.ts` | A11y Helper | | `src/user-settings-store.svelte.ts` | Server-Sync Store | -| `src/custom-themes-store.svelte.ts` | Custom Themes Store | | `src/utils.ts` | Theme Utilities | | `src/app-routes.ts` | Start-Page Konfiguration | @@ -420,11 +368,7 @@ theme: { | `src/components/ThemeCard.svelte` | Theme-Vorschau Karte | | `src/components/ThemeGrid.svelte` | Grid von Theme-Karten | | `src/components/A11ySettings.svelte` | A11y Einstellungen | -| `src/components/editor/` | Theme Editor Komponenten | -| `src/components/community/` | Community Themes Komponenten | | `src/pages/ThemePage.svelte` | VollstΓ€ndige Themes-Seite | -| `src/pages/ThemeEditorPage.svelte` | Theme Editor Seite | -| `src/pages/CommunityThemesPage.svelte` | Community Themes | ## Integration in eine App @@ -485,7 +429,15 @@ export const a11y = createA11yStore({ import { theme, a11y } from '$lib/stores/theme'; - + theme.setVariant(v)} + showModeSelector={true} + currentMode={theme.mode} + onModeChange={(m) => theme.setMode(m)} + a11yStore={a11y} + showA11ySettings={true} +/> ``` ## Vorteile @@ -493,5 +445,4 @@ export const a11y = createA11yStore({ - **Konsistenz:** Alle Apps sehen einheitlich aus - **User Experience:** Theme-Einstellungen werden gespeichert - **Accessibility:** Barrierefreiheit ist eingebaut -- **Anpassbarkeit:** Nutzer kΓΆnnen eigene Themes erstellen -- **Community:** Themes kΓΆnnen geteilt werden +- **Einfachheit:** 8 vordefinierte Themes zur Auswahl diff --git a/packages/shared-theme-ui/src/components/community/CommunityThemeGallery.svelte b/packages/shared-theme-ui/src/components/community/CommunityThemeGallery.svelte deleted file mode 100644 index 907722ead..000000000 --- a/packages/shared-theme-ui/src/components/community/CommunityThemeGallery.svelte +++ /dev/null @@ -1,266 +0,0 @@ - - -
- -
- -
- - -
- - -
- - -
- - - -
- - -
- - {#each commonTags as tag} - - {/each} - - {#if hasActiveFilters} - - {/if} -
- - -
- {#if loading} - Lade... - {:else} - {pagination.total} Themes gefunden - {/if} -
- - -
- {#if themes.length === 0 && !loading} -
-

Keine Themes gefunden

-

Versuche andere Suchbegriffe oder Filter.

- {#if hasActiveFilters} - - {/if} -
- {:else} - {#each themes as theme (theme.id)} - - {/each} - {/if} -
- - - {#if pagination.totalPages > 1} -
- - -
- {pagination.page} - / - {pagination.totalPages} -
- - -
- {/if} -
diff --git a/packages/shared-theme-ui/src/components/community/ThemeCommunityCard.svelte b/packages/shared-theme-ui/src/components/community/ThemeCommunityCard.svelte deleted file mode 100644 index f3451e5ee..000000000 --- a/packages/shared-theme-ui/src/components/community/ThemeCommunityCard.svelte +++ /dev/null @@ -1,197 +0,0 @@ - - -
onSelect?.(theme)} - onkeypress={(e) => e.key === 'Enter' && onSelect?.(theme)} - role="button" - tabindex="0" -> - -
-
-
-
-
-
-
- - {#if theme.isFeatured} -
- - Featured -
- {/if} -
- - -
-
- {theme.emoji} -
-

{theme.name}

- {#if theme.authorName} - von {theme.authorName} - {/if} -
-
- - {#if theme.description} -

{theme.description}

- {/if} - - - {#if theme.tags.length > 0} -
- {#each theme.tags.slice(0, 3) as tag} - {tag} - {/each} - {#if theme.tags.length > 3} - +{theme.tags.length - 3} - {/if} -
- {/if} - - -
-
- - {formatCount(theme.downloadCount)} -
- -
- {#each [1, 2, 3, 4, 5] as star} - - {/each} - ({theme.ratingCount}) -
-
- - -
- {#if showDownload} - - {/if} - - {#if showFavorite} - - {/if} -
-
-
diff --git a/packages/shared-theme-ui/src/components/editor/ColorPicker.svelte b/packages/shared-theme-ui/src/components/editor/ColorPicker.svelte deleted file mode 100644 index 6c2e3a216..000000000 --- a/packages/shared-theme-ui/src/components/editor/ColorPicker.svelte +++ /dev/null @@ -1,349 +0,0 @@ - - -
- {#if label} - - {/if} - -
- -
- - {#if showHexInput} - - {/if} -
- -
- -
- H - - {hsl.h} -
- - -
- S - - {hsl.s}% -
- - -
- L - - {hsl.l}% -
-
-
- - diff --git a/packages/shared-theme-ui/src/components/editor/ThemeEditor.svelte b/packages/shared-theme-ui/src/components/editor/ThemeEditor.svelte deleted file mode 100644 index 697aa4391..000000000 --- a/packages/shared-theme-ui/src/components/editor/ThemeEditor.svelte +++ /dev/null @@ -1,542 +0,0 @@ - - -
- -
-
-
- - -
- -
- - -
-
- -
- Basis: -
- {#each THEME_VARIANTS as variantName} - {@const variant = THEME_DEFINITIONS[variantName]} - - {/each} -
-
-
- - -
-
-

Farben

-
- - -
-
- - -
- {#each MAIN_THEME_COLORS as colorKey} -
- updateColor(colorKey, value)} - compact - /> -
- {/each} -
- - - - - {#if showExtendedColors} -
- {#each EXTENDED_THEME_COLORS as colorKey} -
- updateColor(colorKey, value)} - compact - /> -
- {/each} -
- {/if} -
- - -
- {#if baseVariant} - - {/if} - - {#if showSaveButton && onSave} - - {/if} -
-
- - diff --git a/packages/shared-theme-ui/src/components/editor/ThemeLivePreview.svelte b/packages/shared-theme-ui/src/components/editor/ThemeLivePreview.svelte deleted file mode 100644 index e4dc49686..000000000 --- a/packages/shared-theme-ui/src/components/editor/ThemeLivePreview.svelte +++ /dev/null @@ -1,485 +0,0 @@ - - -
-
- {title} - {#if onModeChange} -
- - -
- {:else} - {mode === 'light' ? 'Hell' : 'Dunkel'} - {/if} -
- -
- -
- -
- - -
-
- - -
- -
-
-
-
-
Max Mustermann
-
Beispiel-Kontakt
-
- -
-
-

Dies ist eine Vorschau, wie dein Theme in der App aussehen wird.

-
-
- - -
-
- - -
- Erfolgreich - Ausstehend - Fehler -
- - -
- -
-
- - -
- - - -
-
-
- - diff --git a/packages/shared-theme-ui/src/index.ts b/packages/shared-theme-ui/src/index.ts index fb0d593bc..546dd0e6d 100644 --- a/packages/shared-theme-ui/src/index.ts +++ b/packages/shared-theme-ui/src/index.ts @@ -12,19 +12,8 @@ export { default as ThemeGrid } from './components/ThemeGrid.svelte'; export { default as A11ySettings } from './components/A11ySettings.svelte'; export { default as A11yQuickToggles } from './components/A11yQuickToggles.svelte'; -// Theme Editor Components -export { default as ColorPicker } from './components/editor/ColorPicker.svelte'; -export { default as ThemeEditor } from './components/editor/ThemeEditor.svelte'; -export { default as ThemeLivePreview } from './components/editor/ThemeLivePreview.svelte'; - -// Community Theme Components -export { default as ThemeCommunityCard } from './components/community/ThemeCommunityCard.svelte'; -export { default as CommunityThemeGallery } from './components/community/CommunityThemeGallery.svelte'; - // Pages export { default as ThemePage } from './pages/ThemePage.svelte'; -export { default as ThemeEditorPage } from './pages/ThemeEditorPage.svelte'; -export { default as CommunityThemesPage } from './pages/CommunityThemesPage.svelte'; // Types export type { diff --git a/packages/shared-theme-ui/src/pages/CommunityThemesPage.svelte b/packages/shared-theme-ui/src/pages/CommunityThemesPage.svelte deleted file mode 100644 index 62d6bf5cc..000000000 --- a/packages/shared-theme-ui/src/pages/CommunityThemesPage.svelte +++ /dev/null @@ -1,312 +0,0 @@ - - -
- -
- {#if onBack} - - {/if} -
-

{title}

-

Entdecke von der Community erstellte Themes

-
-
- - -
- -
- - -
- {#if activeTab === 'browse'} - - {:else if activeTab === 'favorites'} - {#if store.favorites.length === 0 && !store.loading} -
- -

Keine Favoriten

-

Themes, die du favorisierst, werden hier angezeigt.

- -
- {:else} -
- {#each store.favorites as theme (theme.id)} - {@const colors = effectiveMode === 'dark' ? theme.darkColors : theme.lightColors} -
onSelectTheme?.(theme)} - onkeypress={(e) => e.key === 'Enter' && onSelectTheme?.(theme)} - role="button" - tabindex="0" - > -
-
-
-
-
-
-
- {theme.emoji} - {theme.name} -
-
- - -
-
-
- {/each} -
- {/if} - {:else if activeTab === 'downloaded'} - {#if store.downloaded.length === 0 && !store.loading} -
- -

Keine installierten Themes

-

Themes, die du installierst, werden hier angezeigt.

- -
- {:else} -
- {#each store.downloaded as theme (theme.id)} - {@const colors = effectiveMode === 'dark' ? theme.darkColors : theme.lightColors} -
onSelectTheme?.(theme)} - onkeypress={(e) => e.key === 'Enter' && onSelectTheme?.(theme)} - role="button" - tabindex="0" - > -
-
-
-
-
-
-
- {theme.emoji} - {theme.name} -
-
- -
-
-
- {/each} -
- {/if} - {/if} -
-
diff --git a/packages/shared-theme-ui/src/pages/ThemeEditorPage.svelte b/packages/shared-theme-ui/src/pages/ThemeEditorPage.svelte deleted file mode 100644 index 52623a24b..000000000 --- a/packages/shared-theme-ui/src/pages/ThemeEditorPage.svelte +++ /dev/null @@ -1,276 +0,0 @@ - - -
- - - - -
- -
- -
- - -
-
- {#if displayColors} - (previewMode = m)} - /> - {:else} -
-

WΓ€hle eine Basis-Variante oder passe Farben an, um eine Vorschau zu sehen

-
- {/if} -
-
-
-
- - diff --git a/packages/shared-theme-ui/src/pages/ThemePage.svelte b/packages/shared-theme-ui/src/pages/ThemePage.svelte index eac9adee8..b9993f060 100644 --- a/packages/shared-theme-ui/src/pages/ThemePage.svelte +++ b/packages/shared-theme-ui/src/pages/ThemePage.svelte @@ -1,29 +1,11 @@
@@ -158,31 +100,6 @@

- - {#if showCustomThemes && customThemesStore} - - {/if} - {#if showModeSelector && onModeChange}
@@ -207,154 +124,22 @@
{/if} - - {#if !showCustomThemes || activeTab === 'themes'} - -
-

- {t.currentTheme} -

- -
- {:else if activeTab === 'custom' && customThemesStore} - -
-
-

Meine Themes

- {#if onCreateTheme} - - {/if} -
- - {#if customThemesStore.loading} -
Lade...
- {:else if customThemesStore.customThemes.length === 0} -
- -

Noch keine eigenen Themes

-

- Erstelle dein erstes eigenes Theme mit individuellen Farben. -

- {#if onCreateTheme} - - {/if} -
- {:else} -
- {#each customThemesStore.customThemes as theme (theme.id)} -
- -
-
-
-
-
-
-
-
- {theme.emoji} -
-

{theme.name}

- {#if theme.description} -

{theme.description}

- {/if} -
- {#if theme.isPublished} - - VerΓΆffentlicht - - {/if} -
-
- - {#if onEditTheme} - - {/if} - -
-
-
- {/each} -
- {/if} -
- {:else if activeTab === 'community'} - -
-
- -

Community Themes

-

- Entdecke Themes, die von anderen Nutzern erstellt wurden. -

- {#if onCommunityThemes} - - {/if} -
-
- {/if} + +
+

+ {t.currentTheme} +

+ +
{#if showA11ySettings && a11yStore} diff --git a/packages/shared-theme/src/custom-themes-store.svelte.ts b/packages/shared-theme/src/custom-themes-store.svelte.ts deleted file mode 100644 index 888c6a11a..000000000 --- a/packages/shared-theme/src/custom-themes-store.svelte.ts +++ /dev/null @@ -1,506 +0,0 @@ -import type { - CustomTheme, - CommunityTheme, - CreateCustomThemeInput, - UpdateCustomThemeInput, - PublishThemeInput, - CommunityThemeQuery, - PaginatedCommunityThemes, - CustomThemesStore, - CustomThemesStoreConfig, - ThemeColors, - EffectiveMode, -} from './types'; -import { isBrowser } from './utils'; - -/** - * Apply a custom theme's colors to the document as CSS variables - */ -function applyCustomThemeToDocument( - colors: ThemeColors, - effectiveMode: EffectiveMode = 'light' -): void { - if (!isBrowser()) return; - - const root = document.documentElement; - - // Apply all color variables - Object.entries(colors).forEach(([key, value]) => { - // Convert camelCase to kebab-case - const cssVar = key.replace(/([A-Z])/g, '-$1').toLowerCase(); - root.style.setProperty(`--${cssVar}`, value); - }); - - // Set mode class - root.classList.remove('light', 'dark'); - root.classList.add(effectiveMode); - - // Mark as custom theme - root.setAttribute('data-custom-theme', 'true'); -} - -/** - * Clear custom theme and revert to standard theme - */ -function clearCustomThemeFromDocument(): void { - if (!isBrowser()) return; - - const root = document.documentElement; - - // Remove custom theme marker - root.removeAttribute('data-custom-theme'); - - // Clear inline styles (CSS vars will fall back to theme variant) - root.style.cssText = ''; -} - -/** - * Create a custom themes store for managing user's custom themes and community themes - * - * @example - * ```typescript - * import { createCustomThemesStore } from '@manacore/shared-theme'; - * import { authStore } from '$lib/stores/auth.svelte'; - * - * export const customThemesStore = createCustomThemesStore({ - * authUrl: import.meta.env.PUBLIC_AUTH_URL, - * getAccessToken: () => authStore.getAccessToken(), - * }); - * ``` - */ -export function createCustomThemesStore(config: CustomThemesStoreConfig): CustomThemesStore { - const { authUrl, getAccessToken } = config; - - // State - let customThemes = $state([]); - let communityThemes = $state([]); - let favorites = $state([]); - let downloaded = $state([]); - let pagination = $state({ page: 1, totalPages: 1, total: 0 }); - let loading = $state(false); - let error = $state(null); - - // Track currently applied custom theme - let appliedThemeId = $state(null); - - /** - * Make an authenticated API request - */ - async function apiRequest(endpoint: string, options: RequestInit = {}): Promise { - const token = await getAccessToken(); - if (!token) { - throw new Error('Not authenticated'); - } - - const url = `${authUrl}${endpoint}`; - const response = await fetch(url, { - ...options, - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - ...options.headers, - }, - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `Request failed: ${response.status}`); - } - - // Handle 204 No Content - if (response.status === 204) { - return undefined as T; - } - - return response.json(); - } - - /** - * Make a public API request (no auth required) - */ - async function publicApiRequest(endpoint: string, options: RequestInit = {}): Promise { - const url = `${authUrl}${endpoint}`; - const token = await getAccessToken(); - - const headers: Record = { - 'Content-Type': 'application/json', - }; - - // Add auth if available (for user-specific data like favorites) - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - const response = await fetch(url, { - ...options, - headers: { - ...headers, - ...options.headers, - }, - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `Request failed: ${response.status}`); - } - - return response.json(); - } - - // ==================== Custom Theme Operations ==================== - - /** - * Load user's custom themes - */ - async function loadCustomThemes(): Promise { - loading = true; - error = null; - - try { - customThemes = await apiRequest('/api/v1/themes'); - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to load themes'; - throw err; - } finally { - loading = false; - } - } - - /** - * Create a new custom theme - */ - async function createTheme(input: CreateCustomThemeInput): Promise { - loading = true; - error = null; - - try { - const theme = await apiRequest('/api/v1/themes', { - method: 'POST', - body: JSON.stringify(input), - }); - customThemes = [...customThemes, theme]; - return theme; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to create theme'; - throw err; - } finally { - loading = false; - } - } - - /** - * Update an existing custom theme - */ - async function updateTheme(id: string, input: UpdateCustomThemeInput): Promise { - loading = true; - error = null; - - try { - const theme = await apiRequest(`/api/v1/themes/${id}`, { - method: 'PATCH', - body: JSON.stringify(input), - }); - customThemes = customThemes.map((t) => (t.id === id ? theme : t)); - return theme; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to update theme'; - throw err; - } finally { - loading = false; - } - } - - /** - * Delete a custom theme - */ - async function deleteTheme(id: string): Promise { - loading = true; - error = null; - - try { - await apiRequest(`/api/v1/themes/${id}`, { - method: 'DELETE', - }); - customThemes = customThemes.filter((t) => t.id !== id); - - // Clear applied theme if it was the deleted one - if (appliedThemeId === id) { - clearCustomTheme(); - } - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to delete theme'; - throw err; - } finally { - loading = false; - } - } - - /** - * Publish a custom theme to the community - */ - async function publishTheme(id: string, input?: PublishThemeInput): Promise { - loading = true; - error = null; - - try { - const communityTheme = await apiRequest(`/api/v1/themes/${id}/publish`, { - method: 'POST', - body: JSON.stringify(input || {}), - }); - - // Update the custom theme's isPublished status - customThemes = customThemes.map((t) => (t.id === id ? { ...t, isPublished: true } : t)); - - return communityTheme; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to publish theme'; - throw err; - } finally { - loading = false; - } - } - - // ==================== Community Theme Operations ==================== - - /** - * Browse community themes with filtering/sorting - */ - async function browseCommunity(query?: CommunityThemeQuery): Promise { - loading = true; - error = null; - - try { - const params = new URLSearchParams(); - if (query?.page) params.set('page', String(query.page)); - if (query?.limit) params.set('limit', String(query.limit)); - if (query?.sort) params.set('sort', query.sort); - if (query?.search) params.set('search', query.search); - if (query?.authorId) params.set('authorId', query.authorId); - if (query?.featuredOnly) params.set('featuredOnly', 'true'); - if (query?.tags?.length) { - query.tags.forEach((tag) => params.append('tags', tag)); - } - - const queryString = params.toString(); - const endpoint = `/api/v1/community-themes${queryString ? `?${queryString}` : ''}`; - - const result = await publicApiRequest(endpoint); - communityThemes = result.themes; - pagination = { - page: result.page, - totalPages: result.totalPages, - total: result.total, - }; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to browse community themes'; - throw err; - } finally { - loading = false; - } - } - - /** - * Download/install a community theme - */ - async function downloadTheme(id: string): Promise { - loading = true; - error = null; - - try { - const theme = await apiRequest(`/api/v1/community-themes/${id}/download`, { - method: 'POST', - }); - - // Update download status in community themes list - communityThemes = communityThemes.map((t) => - t.id === id ? { ...t, isDownloaded: true, downloadCount: theme.downloadCount } : t - ); - - // Add to downloaded list if not already there - if (!downloaded.some((t) => t.id === id)) { - downloaded = [...downloaded, theme]; - } - - return theme; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to download theme'; - throw err; - } finally { - loading = false; - } - } - - /** - * Rate a community theme - */ - async function rateTheme( - id: string, - rating: number - ): Promise<{ averageRating: number; ratingCount: number }> { - error = null; - - try { - const result = await apiRequest<{ averageRating: number; ratingCount: number }>( - `/api/v1/community-themes/${id}/rate`, - { - method: 'POST', - body: JSON.stringify({ rating }), - } - ); - - // Update rating in community themes list - communityThemes = communityThemes.map((t) => - t.id === id - ? { - ...t, - averageRating: result.averageRating, - ratingCount: result.ratingCount, - userRating: rating, - } - : t - ); - - return result; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to rate theme'; - throw err; - } - } - - /** - * Toggle favorite status for a community theme - */ - async function toggleFavorite(id: string): Promise<{ isFavorited: boolean }> { - error = null; - - try { - const result = await apiRequest<{ isFavorited: boolean }>( - `/api/v1/community-themes/${id}/favorite`, - { method: 'POST' } - ); - - // Update favorite status in community themes list - communityThemes = communityThemes.map((t) => - t.id === id ? { ...t, isFavorited: result.isFavorited } : t - ); - - // Update favorites list - if (result.isFavorited) { - const theme = communityThemes.find((t) => t.id === id); - if (theme && !favorites.some((t) => t.id === id)) { - favorites = [...favorites, { ...theme, isFavorited: true }]; - } - } else { - favorites = favorites.filter((t) => t.id !== id); - } - - return result; - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to toggle favorite'; - throw err; - } - } - - /** - * Load user's favorite themes - */ - async function loadFavorites(): Promise { - loading = true; - error = null; - - try { - favorites = await apiRequest('/api/v1/community-themes/favorites'); - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to load favorites'; - throw err; - } finally { - loading = false; - } - } - - /** - * Load user's downloaded themes - */ - async function loadDownloaded(): Promise { - loading = true; - error = null; - - try { - downloaded = await apiRequest('/api/v1/community-themes/downloaded'); - } catch (err) { - error = err instanceof Error ? err.message : 'Failed to load downloaded themes'; - throw err; - } finally { - loading = false; - } - } - - // ==================== Apply Theme ==================== - - /** - * Apply a custom or community theme to the document - */ - function applyCustomTheme(theme: CustomTheme | CommunityTheme): void { - // Determine effective mode from system or stored preference - const effectiveMode: EffectiveMode = isBrowser() - ? window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light' - : 'light'; - - const colors = effectiveMode === 'dark' ? theme.darkColors : theme.lightColors; - applyCustomThemeToDocument(colors as ThemeColors, effectiveMode); - appliedThemeId = theme.id; - } - - /** - * Clear the applied custom theme and revert to standard theme - */ - function clearCustomTheme(): void { - clearCustomThemeFromDocument(); - appliedThemeId = null; - } - - return { - get customThemes() { - return customThemes; - }, - get communityThemes() { - return communityThemes; - }, - get favorites() { - return favorites; - }, - get downloaded() { - return downloaded; - }, - get pagination() { - return pagination; - }, - get loading() { - return loading; - }, - get error() { - return error; - }, - - // Custom theme operations - loadCustomThemes, - createTheme, - updateTheme, - deleteTheme, - publishTheme, - - // Community theme operations - browseCommunity, - downloadTheme, - rateTheme, - toggleFavorite, - loadFavorites, - loadDownloaded, - - // Apply theme - applyCustomTheme, - clearCustomTheme, - }; -} diff --git a/packages/shared-theme/src/index.ts b/packages/shared-theme/src/index.ts index 9a5e51864..0a3881ab5 100644 --- a/packages/shared-theme/src/index.ts +++ b/packages/shared-theme/src/index.ts @@ -28,18 +28,6 @@ export type { StartPageConfig, WeekStartDay, GeneralSettings, - // Custom & Community Themes Types - ThemeColorsInput, - CustomTheme, - CreateCustomThemeInput, - UpdateCustomThemeInput, - CommunityTheme, - CommunityThemeQuery, - PaginatedCommunityThemes, - PublishThemeInput, - ThemeEditorState, - CustomThemesStore, - CustomThemesStoreConfig, } from './types'; // User Settings Constants @@ -48,9 +36,6 @@ export { DEFAULT_GLOBAL_SETTINGS, DEFAULT_GENERAL_SETTINGS } from './types'; // Theme Variant Categories export { DEFAULT_THEME_VARIANTS, EXTENDED_THEME_VARIANTS } from './types'; -// Custom Theme Constants -export { MAIN_THEME_COLORS, EXTENDED_THEME_COLORS, THEME_COLOR_LABELS } from './types'; - // Constants export { THEME_VARIANTS, @@ -81,9 +66,6 @@ export { createA11yStore } from './a11y-store.svelte'; // User Settings Store export { createUserSettingsStore } from './user-settings-store.svelte'; -// Custom Themes Store -export { createCustomThemesStore } from './custom-themes-store.svelte'; - // Utils export { isBrowser, diff --git a/packages/shared-theme/src/types.ts b/packages/shared-theme/src/types.ts index 0ff3d8dfe..6f7949ebf 100644 --- a/packages/shared-theme/src/types.ts +++ b/packages/shared-theme/src/types.ts @@ -359,7 +359,7 @@ export const DEFAULT_GENERAL_SETTINGS: GeneralSettings = { * Default global settings */ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = { - nav: { desktopPosition: 'top', sidebarCollapsed: false, hiddenNavItems: {} }, + nav: { desktopPosition: 'bottom', sidebarCollapsed: false, hiddenNavItems: {} }, theme: { mode: 'system', colorScheme: 'ocean', pinnedThemes: [] }, locale: 'de', general: DEFAULT_GENERAL_SETTINGS,