From 4e4db4612cb90052623095a8d79535097ee4bbb0 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:48:00 +0100 Subject: [PATCH] feat(apps): add unified Apps page and PillNavigation to all web apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add AppsPage component to shared-ui for displaying all Mana apps - Add allAppsHref prop to PillNavigation with "Alle Apps" link in dropdown - Integrate PillNavigation in archived apps (maerchenzauber, news, uload, wisekeep) - Add /apps route to all web apps (active and archived) - Replace custom sidebars/headers with unified PillNavigation Apps updated: - Active: chat, manacore, manadeck, picture, presi, zitare - Archived: maerchenzauber, memoro, news, nutriphi, uload, wisekeep 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../web/src/routes/(protected)/+layout.svelte | 298 +++------ .../src/routes/(protected)/apps/+page.svelte | 17 + .../web/src/routes/(protected)/+layout.svelte | 256 ++++---- .../src/routes/(protected)/apps/+page.svelte | 17 + .../apps/web/src/routes/(app)/+layout.svelte | 240 ++++--- .../web/src/routes/(app)/apps/+page.svelte | 17 + .../web/src/routes/(protected)/+layout.svelte | 208 +++--- .../src/routes/(protected)/apps/+page.svelte | 17 + .../web/src/routes/(protected)/+layout.svelte | 1 + .../src/routes/(protected)/apps/+page.svelte | 14 + .../apps/web/src/routes/(app)/+layout.svelte | 1 + .../web/src/routes/(app)/apps/+page.svelte | 14 + .../apps/web/src/routes/(app)/+layout.svelte | 5 +- .../web/src/routes/(app)/apps/+page.svelte | 14 + .../apps/web/src/routes/app/+layout.svelte | 1 + .../apps/web/src/routes/app/apps/+page.svelte | 14 + apps/presi/apps/web/src/routes/+layout.svelte | 9 +- .../apps/web/src/routes/apps/+page.svelte | 14 + .../zitare/apps/web/src/routes/+layout.svelte | 28 +- .../apps/web/src/routes/apps/+page.svelte | 14 + packages/shared-ui/package.json | 1 + packages/shared-ui/src/index.ts | 3 + .../src/navigation/PillNavigation.svelte | 70 +- packages/shared-ui/src/pages/AppsPage.svelte | 616 ++++++++++++++++++ pnpm-lock.yaml | 3 + 25 files changed, 1354 insertions(+), 538 deletions(-) create mode 100644 apps-archived/maerchenzauber/apps/web/src/routes/(protected)/apps/+page.svelte create mode 100644 apps-archived/news/apps/web/src/routes/(protected)/apps/+page.svelte create mode 100644 apps-archived/uload/apps/web/src/routes/(app)/apps/+page.svelte create mode 100644 apps-archived/wisekeep/apps/web/src/routes/(protected)/apps/+page.svelte create mode 100644 apps/chat/apps/web/src/routes/(protected)/apps/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/apps/+page.svelte create mode 100644 apps/manadeck/apps/web/src/routes/(app)/apps/+page.svelte create mode 100644 apps/picture/apps/web/src/routes/app/apps/+page.svelte create mode 100644 apps/presi/apps/web/src/routes/apps/+page.svelte create mode 100644 apps/zitare/apps/web/src/routes/apps/+page.svelte create mode 100644 packages/shared-ui/src/pages/AppsPage.svelte diff --git a/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/+layout.svelte b/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/+layout.svelte index b51012de7..488c7096e 100644 --- a/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/+layout.svelte +++ b/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/+layout.svelte @@ -25,122 +25,51 @@ let { children } = $props(); let loading = $state(true); - let isSidebarCollapsed = $state(false); - let isMobileMenuOpen = $state(false); - let showKeyboardShortcuts = $state(false); + let isSidebarMode = $state(false); + let isCollapsed = $state(false); + let isDark = $state(false); - // Keyboard shortcuts configuration - const navRoutes: Record = { - '1': '/dashboard', // Dashboard - '2': '/stories', // Stories - '3': '/characters', // Characters - '4': '/discover', // Discover - '5': '/settings', // Settings - }; - - const actionRoutes: Record = { - n: '/stories/create', // New Story - s: '/stories/create', // New Story (alternative) - c: '/characters/create', // New Character - }; - - // Shortcut descriptions for help modal - const shortcutGroups = [ - { - title: 'Navigation', - shortcuts: [ - { keys: ['Cmd/Ctrl', '1'], description: 'Dashboard' }, - { keys: ['Cmd/Ctrl', '2'], description: 'Geschichten' }, - { keys: ['Cmd/Ctrl', '3'], description: 'Charaktere' }, - { keys: ['Cmd/Ctrl', '4'], description: 'Entdecken' }, - { keys: ['Cmd/Ctrl', '5'], description: 'Einstellungen' }, - ], - }, - { - title: 'Aktionen', - shortcuts: [ - { keys: ['Cmd/Ctrl', 'N'], description: 'Neue Geschichte' }, - { keys: ['Cmd/Ctrl', 'Shift', 'C'], description: 'Neuer Charakter' }, - { keys: ['?'], description: 'Tastaturkürzel anzeigen' }, - ], - }, - { - title: 'Allgemein', - shortcuts: [ - { keys: ['Esc'], description: 'Menü/Modal schließen' }, - { keys: ['B'], description: 'Seitenleiste ein-/ausblenden' }, - ], - }, - ]; + // Navigation shortcuts (Ctrl+1-5) + const navRoutes = ['/dashboard', '/stories', '/characters', '/discover', '/settings']; function handleKeydown(event: KeyboardEvent) { - // Don't handle if user is typing in an input const target = event.target as HTMLElement; if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { return; } - // ? to show keyboard shortcuts - if (event.key === '?' && !event.ctrlKey && !event.metaKey) { - event.preventDefault(); - showKeyboardShortcuts = !showKeyboardShortcuts; - return; - } - - // ESC to close modals/menus - if (event.key === 'Escape') { - if (showKeyboardShortcuts) { - showKeyboardShortcuts = false; - return; - } - if (isMobileMenuOpen) { - isMobileMenuOpen = false; - return; - } - } - - // B to toggle sidebar (without modifiers) - if (event.key === 'b' && !event.ctrlKey && !event.metaKey && !event.shiftKey) { - event.preventDefault(); - handleSidebarToggle(); - return; - } - - // Ctrl/Cmd + key shortcuts - if ((event.ctrlKey || event.metaKey) && !event.altKey) { - // Ctrl/Cmd + number for navigation - const route = navRoutes[event.key]; - if (route) { + if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { + const num = parseInt(event.key); + if (num >= 1 && num <= 5) { event.preventDefault(); - goto(route); - return; - } - - // Ctrl/Cmd + Shift + C for new character - if (event.shiftKey && event.key.toLowerCase() === 'c') { - event.preventDefault(); - goto('/characters/create'); - return; - } - - // Ctrl/Cmd + N for new story - if (!event.shiftKey && event.key.toLowerCase() === 'n') { - event.preventDefault(); - goto('/stories/create'); - return; + const route = navRoutes[num - 1]; + if (route) { + goto(route); + } } } } - function handleSidebarToggle() { - isSidebarCollapsed = !isSidebarCollapsed; + function handleModeChange(isSidebar: boolean) { + isSidebarMode = isSidebar; if (typeof localStorage !== 'undefined') { - localStorage.setItem('maerchenzauber-sidebar-collapsed', String(isSidebarCollapsed)); + localStorage.setItem('maerchenzauber-nav-sidebar', String(isSidebar)); } } - function handleMobileMenuToggle() { - isMobileMenuOpen = !isMobileMenuOpen; + function handleCollapsedChange(collapsed: boolean) { + isCollapsed = collapsed; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('maerchenzauber-nav-collapsed', String(collapsed)); + } + } + + function handleToggleTheme() { + isDark = !isDark; + document.documentElement.classList.toggle('dark', isDark); + if (typeof localStorage !== 'undefined') { + localStorage.setItem('maerchenzauber-dark-mode', String(isDark)); + } } async function handleLogout() { @@ -148,7 +77,6 @@ goto('/login'); } - // Client-side auth guard onMount(async () => { await authStore.initialize(); @@ -157,11 +85,20 @@ return; } - // Restore sidebar state from localStorage + // Restore nav mode from localStorage if (typeof localStorage !== 'undefined') { - const savedCollapsed = localStorage.getItem('maerchenzauber-sidebar-collapsed'); + const savedSidebar = localStorage.getItem('maerchenzauber-nav-sidebar'); + if (savedSidebar === 'true') { + isSidebarMode = true; + } + const savedCollapsed = localStorage.getItem('maerchenzauber-nav-collapsed'); if (savedCollapsed === 'true') { - isSidebarCollapsed = true; + isCollapsed = true; + } + const savedDark = localStorage.getItem('maerchenzauber-dark-mode'); + if (savedDark === 'true') { + isDark = true; + document.documentElement.classList.add('dark'); } } @@ -172,10 +109,7 @@ {#if loading} - -
+
{:else} - -
- - + - - - {#if isMobileMenuOpen} -
(isMobileMenuOpen = false)} - onkeydown={(e) => e.key === 'Escape' && (isMobileMenuOpen = false)} - role="button" - tabindex="0" - >
-
- (isMobileMenuOpen = false)} - onLogout={handleLogout} - isMobile={true} - /> -
- {/if} - - -
- -
+ {#snippet logo()} + + Märchenzauber + {/snippet} + - -
+
+
{@render children()} -
-
+
+
- - - - {#if showKeyboardShortcuts} -
(showKeyboardShortcuts = false)} - onkeydown={(e) => e.key === 'Escape' && (showKeyboardShortcuts = false)} - role="button" - tabindex="0" - > -
e.stopPropagation()} - onkeydown={(e) => e.stopPropagation()} - role="dialog" - > -
-

Tastaturkürzel

- -
- -
- {#each shortcutGroups as group} -
-

- {group.title} -

-
- {#each group.shortcuts as shortcut} -
- {shortcut.description} -
- {#each shortcut.keys as key} - - {key} - - {/each} -
-
- {/each} -
-
- {/each} -
- -

- Drücke ? um dieses - Menü zu öffnen -

-
-
- {/if} {/if} diff --git a/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/apps/+page.svelte b/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/apps/+page.svelte new file mode 100644 index 000000000..06e64d891 --- /dev/null +++ b/apps-archived/maerchenzauber/apps/web/src/routes/(protected)/apps/+page.svelte @@ -0,0 +1,17 @@ + + + + Alle Apps - Märchenzauber + + +
+ +
+ + diff --git a/apps-archived/news/apps/web/src/routes/(protected)/+layout.svelte b/apps-archived/news/apps/web/src/routes/(protected)/+layout.svelte index addac4154..57185e315 100644 --- a/apps-archived/news/apps/web/src/routes/(protected)/+layout.svelte +++ b/apps-archived/news/apps/web/src/routes/(protected)/+layout.svelte @@ -2,135 +2,153 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { authStore } from '$lib/stores/auth.svelte'; + import { onMount } from 'svelte'; + import { PillNavigation } from '@manacore/shared-ui'; + import type { PillNavItem } from '@manacore/shared-ui'; + import { getPillAppItems } from '@manacore/shared-branding'; let { children } = $props(); - const navItems = [ - { href: '/feed', label: 'Feed', icon: 'feed' }, - { href: '/summaries', label: 'Zusammenfassungen', icon: 'summaries' }, - { href: '/in-depth', label: 'In-Depth', icon: 'indepth' }, - { href: '/saved', label: 'Gespeichert', icon: 'saved' }, + // App switcher items + const appItems = getPillAppItems('news'); + + // User email for dropdown + let userEmail = $derived(authStore.user?.email); + + // Navigation items for News + const navItems: PillNavItem[] = [ + { href: '/feed', label: 'Feed', icon: 'rss' }, + { href: '/summaries', label: 'Zusammenfassungen', icon: 'document' }, + { href: '/in-depth', label: 'In-Depth', icon: 'book' }, + { href: '/saved', label: 'Gespeichert', icon: 'bookmark' }, + { href: '/settings', label: 'Einstellungen', icon: 'settings' }, ]; + let loading = $state(true); + let isSidebarMode = $state(false); + let isCollapsed = $state(false); + let isDark = $state(false); + + // Navigation shortcuts (Ctrl+1-5) + const navRoutes = ['/feed', '/summaries', '/in-depth', '/saved', '/settings']; + + function handleKeydown(event: KeyboardEvent) { + const target = event.target as HTMLElement; + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { + return; + } + + if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { + const num = parseInt(event.key); + if (num >= 1 && num <= 5) { + event.preventDefault(); + const route = navRoutes[num - 1]; + if (route) { + goto(route); + } + } + } + } + + function handleModeChange(isSidebar: boolean) { + isSidebarMode = isSidebar; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('news-nav-sidebar', String(isSidebar)); + } + } + + function handleCollapsedChange(collapsed: boolean) { + isCollapsed = collapsed; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('news-nav-collapsed', String(collapsed)); + } + } + + function handleToggleTheme() { + isDark = !isDark; + document.documentElement.classList.toggle('dark', isDark); + if (typeof localStorage !== 'undefined') { + localStorage.setItem('news-dark-mode', String(isDark)); + } + } + async function handleLogout() { await authStore.logout(); goto('/auth/login'); } + + onMount(() => { + // Restore nav mode from localStorage + if (typeof localStorage !== 'undefined') { + const savedSidebar = localStorage.getItem('news-nav-sidebar'); + if (savedSidebar === 'true') { + isSidebarMode = true; + } + const savedCollapsed = localStorage.getItem('news-nav-collapsed'); + if (savedCollapsed === 'true') { + isCollapsed = true; + } + const savedDark = localStorage.getItem('news-dark-mode'); + if (savedDark === 'true') { + isDark = true; + document.documentElement.classList.add('dark'); + } + } + loading = false; + }); - +
+
+ {@render children()} +
+
+
+{/if} diff --git a/apps-archived/news/apps/web/src/routes/(protected)/apps/+page.svelte b/apps-archived/news/apps/web/src/routes/(protected)/apps/+page.svelte new file mode 100644 index 000000000..7bbd76a08 --- /dev/null +++ b/apps-archived/news/apps/web/src/routes/(protected)/apps/+page.svelte @@ -0,0 +1,17 @@ + + + + Alle Apps - News + + +
+ +
+ + diff --git a/apps-archived/uload/apps/web/src/routes/(app)/+layout.svelte b/apps-archived/uload/apps/web/src/routes/(app)/+layout.svelte index 03af825eb..4761483b3 100644 --- a/apps-archived/uload/apps/web/src/routes/(app)/+layout.svelte +++ b/apps-archived/uload/apps/web/src/routes/(app)/+layout.svelte @@ -1,39 +1,100 @@ - -
+ - - - - - (mobileMenuOpen = false)} /> - - -{#if data.user} - -{/if} +
+{:else} +
+ + {#snippet logo()} + 🔗 + uload + {/snippet} + - -
- {@render children?.()} -
+
+
+ {@render children?.()} +
+
+
+{/if} diff --git a/apps-archived/uload/apps/web/src/routes/(app)/apps/+page.svelte b/apps-archived/uload/apps/web/src/routes/(app)/apps/+page.svelte new file mode 100644 index 000000000..2a8074e34 --- /dev/null +++ b/apps-archived/uload/apps/web/src/routes/(app)/apps/+page.svelte @@ -0,0 +1,17 @@ + + + + Alle Apps - uload + + +
+ +
+ + diff --git a/apps-archived/wisekeep/apps/web/src/routes/(protected)/+layout.svelte b/apps-archived/wisekeep/apps/web/src/routes/(protected)/+layout.svelte index 34ba978f2..37b2d5126 100644 --- a/apps-archived/wisekeep/apps/web/src/routes/(protected)/+layout.svelte +++ b/apps-archived/wisekeep/apps/web/src/routes/(protected)/+layout.svelte @@ -5,10 +5,79 @@ import { authStore } from '$lib/stores/auth.svelte'; import { initWebSocket, cleanup, isConnected } from '$lib/stores/jobs'; import type { LayoutData } from './$types'; + import { PillNavigation } from '@manacore/shared-ui'; + import type { PillNavItem } from '@manacore/shared-ui'; + import { getPillAppItems } from '@manacore/shared-branding'; let { children, data }: { children: any; data: LayoutData } = $props(); + // App switcher items + const appItems = getPillAppItems('wisekeep'); + + // User email for dropdown + let userEmail = $derived(authStore.user?.email); + + // Navigation items for Wisekeep + const navItems: PillNavItem[] = [ + { href: '/dashboard', label: 'Dashboard', icon: 'home' }, + { href: '/transcribe', label: 'Transcribe', icon: 'mic' }, + { href: '/transcripts', label: 'Transcripts', icon: 'document' }, + { href: '/playlists', label: 'Playlists', icon: 'list' }, + { href: '/settings', label: 'Settings', icon: 'settings' }, + ]; + let isChecking = $state(true); + let isSidebarMode = $state(false); + let isCollapsed = $state(false); + let isDark = $state(false); + + // Navigation shortcuts (Ctrl+1-5) + const navRoutes = ['/dashboard', '/transcribe', '/transcripts', '/playlists', '/settings']; + + function handleKeydown(event: KeyboardEvent) { + const target = event.target as HTMLElement; + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { + return; + } + + if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { + const num = parseInt(event.key); + if (num >= 1 && num <= 5) { + event.preventDefault(); + const route = navRoutes[num - 1]; + if (route) { + goto(route); + } + } + } + } + + function handleModeChange(isSidebar: boolean) { + isSidebarMode = isSidebar; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('wisekeep-nav-sidebar', String(isSidebar)); + } + } + + function handleCollapsedChange(collapsed: boolean) { + isCollapsed = collapsed; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('wisekeep-nav-collapsed', String(collapsed)); + } + } + + function handleToggleTheme() { + isDark = !isDark; + document.documentElement.classList.toggle('dark', isDark); + if (typeof localStorage !== 'undefined') { + localStorage.setItem('wisekeep-dark-mode', String(isDark)); + } + } + + async function handleSignOut() { + await authStore.signOut(); + goto('/login'); + } // Check auth on mount and redirect if not authenticated onMount(async () => { @@ -27,6 +96,23 @@ shouldRedirect = true; } + // Restore nav mode from localStorage + if (typeof localStorage !== 'undefined') { + const savedSidebar = localStorage.getItem('wisekeep-nav-sidebar'); + if (savedSidebar === 'true') { + isSidebarMode = true; + } + const savedCollapsed = localStorage.getItem('wisekeep-nav-collapsed'); + if (savedCollapsed === 'true') { + isCollapsed = true; + } + const savedDark = localStorage.getItem('wisekeep-dark-mode'); + if (savedDark === 'true') { + isDark = true; + document.documentElement.classList.add('dark'); + } + } + // Always set isChecking to false isChecking = false; @@ -38,87 +124,59 @@ // Return cleanup function return () => cleanup(); }); - - async function handleSignOut() { - await authStore.signOut(); - goto('/login'); - } + + {#if isChecking} - -
-
+
+
+
+

Laden...

+
{:else} -
-
-
- Wisekeep - -
-
- - - {$isConnected ? 'Connected' : 'Disconnected'} - -
- {#if authStore.user} - - {/if} - -
-
-
+
+ + {#snippet logo()} + 🧠 + Wisekeep + {/snippet} + -
- {@render children()} +
+
+ {@render children()} +
- -
-
- Wisekeep - AI-powered wisdom extraction from video -
-
{/if} diff --git a/apps-archived/wisekeep/apps/web/src/routes/(protected)/apps/+page.svelte b/apps-archived/wisekeep/apps/web/src/routes/(protected)/apps/+page.svelte new file mode 100644 index 000000000..ff8d569cb --- /dev/null +++ b/apps-archived/wisekeep/apps/web/src/routes/(protected)/apps/+page.svelte @@ -0,0 +1,17 @@ + + + + Alle Apps - Wisekeep + + +
+ +
+ + diff --git a/apps/chat/apps/web/src/routes/(protected)/+layout.svelte b/apps/chat/apps/web/src/routes/(protected)/+layout.svelte index 7dc8c44b4..c89e2e00c 100644 --- a/apps/chat/apps/web/src/routes/(protected)/+layout.svelte +++ b/apps/chat/apps/web/src/routes/(protected)/+layout.svelte @@ -205,6 +205,7 @@ settingsHref="/settings" manaHref="/mana" profileHref="/profile" + allAppsHref="/apps" /> diff --git a/apps/chat/apps/web/src/routes/(protected)/apps/+page.svelte b/apps/chat/apps/web/src/routes/(protected)/apps/+page.svelte new file mode 100644 index 000000000..b16dac047 --- /dev/null +++ b/apps/chat/apps/web/src/routes/(protected)/apps/+page.svelte @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte index d7c562299..811077ccb 100644 --- a/apps/manacore/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/+layout.svelte @@ -191,6 +191,7 @@ settingsHref="/settings" manaHref="/mana" profileHref="/profile" + allAppsHref="/apps" /> diff --git a/apps/manacore/apps/web/src/routes/(app)/apps/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/apps/+page.svelte new file mode 100644 index 000000000..54767328e --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/apps/+page.svelte @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte b/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte index ac1729a68..e9454f691 100644 --- a/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/manadeck/apps/web/src/routes/(app)/+layout.svelte @@ -27,13 +27,11 @@ // Get theme state let isDark = $derived(theme.isDark); - // Navigation items for ManaDeck + // Navigation items for ManaDeck (Mana and Profile are in user dropdown) const navItems: PillNavItem[] = [ { href: '/decks', label: 'Decks', icon: 'archive' }, { href: '/explore', label: 'Explore', icon: 'search' }, { href: '/progress', label: 'Progress', icon: 'chart' }, - { href: '/mana', label: 'Mana', icon: 'mana' }, - { href: '/profile', label: 'Profile', icon: 'user' }, ]; // Theme variant dropdown items @@ -196,6 +194,7 @@ settingsHref="/settings" manaHref="/mana" profileHref="/profile" + allAppsHref="/apps" /> diff --git a/apps/manadeck/apps/web/src/routes/(app)/apps/+page.svelte b/apps/manadeck/apps/web/src/routes/(app)/apps/+page.svelte new file mode 100644 index 000000000..1be038a3b --- /dev/null +++ b/apps/manadeck/apps/web/src/routes/(app)/apps/+page.svelte @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/apps/picture/apps/web/src/routes/app/+layout.svelte b/apps/picture/apps/web/src/routes/app/+layout.svelte index aa70f044f..40cd5fcc8 100644 --- a/apps/picture/apps/web/src/routes/app/+layout.svelte +++ b/apps/picture/apps/web/src/routes/app/+layout.svelte @@ -239,6 +239,7 @@ settingsHref="/app/settings" manaHref="/app/mana" profileHref="/app/profile" + allAppsHref="/app/apps" /> {/if} diff --git a/apps/picture/apps/web/src/routes/app/apps/+page.svelte b/apps/picture/apps/web/src/routes/app/apps/+page.svelte new file mode 100644 index 000000000..aa89aaa1b --- /dev/null +++ b/apps/picture/apps/web/src/routes/app/apps/+page.svelte @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/apps/presi/apps/web/src/routes/+layout.svelte b/apps/presi/apps/web/src/routes/+layout.svelte index 95b6ea3de..b0de65340 100644 --- a/apps/presi/apps/web/src/routes/+layout.svelte +++ b/apps/presi/apps/web/src/routes/+layout.svelte @@ -61,12 +61,8 @@ let userEmail = $derived(auth.user?.email); // Navigation items for Presi - const navItems: PillNavItem[] = [ - { href: '/', label: 'Decks', icon: 'document' }, - { href: '/profile', label: 'Profil', icon: 'user' }, - { href: '/mana', label: 'Mana', icon: 'sparkles' }, - { href: '/settings', label: 'Einstellungen', icon: 'settings' }, - ]; + // Profile, Mana, and Settings are in the user dropdown via profileHref, manaHref, settingsHref + const navItems: PillNavItem[] = [{ href: '/', label: 'Decks', icon: 'document' }]; // Public routes that don't require auth const publicRoutes = ['/login', '/register', '/forgot-password']; @@ -205,6 +201,7 @@ settingsHref="/settings" manaHref="/mana" profileHref="/profile" + allAppsHref="/apps" /> diff --git a/apps/presi/apps/web/src/routes/apps/+page.svelte b/apps/presi/apps/web/src/routes/apps/+page.svelte new file mode 100644 index 000000000..50e7d865c --- /dev/null +++ b/apps/presi/apps/web/src/routes/apps/+page.svelte @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/apps/zitare/apps/web/src/routes/+layout.svelte b/apps/zitare/apps/web/src/routes/+layout.svelte index 973c5d101..13f7934c8 100644 --- a/apps/zitare/apps/web/src/routes/+layout.svelte +++ b/apps/zitare/apps/web/src/routes/+layout.svelte @@ -68,7 +68,7 @@ // Navigation items for Zitare const navItems: PillNavItem[] = [ - { href: '/', label: 'Zitate', icon: 'chat' }, + { href: '/', label: 'Zitate', icon: 'document' }, { href: '/search', label: 'Suche', icon: 'search' }, { href: '/authors', label: 'Autoren', icon: 'users' }, { href: '/favorites', label: 'Favoriten', icon: 'heart' }, @@ -214,10 +214,15 @@ class="main-content bg-background" class:sidebar-mode={isSidebarMode && !isCollapsed} class:floating-mode={!isSidebarMode && !isCollapsed} + class:full-height={$page.url.pathname === '/'} > -
+ {#if $page.url.pathname === '/'} {@render children()} -
+ {:else} +
+ {@render children()} +
+ {/if}
{/if} @@ -244,6 +249,23 @@ padding-left: 180px; } + /* Full height mode for scrollable pages like home */ + .main-content.full-height { + display: flex; + flex-direction: column; + height: calc(100vh - 100px); + overflow: hidden; + } + + .main-content.full-height.floating-mode { + height: 100vh; + padding-top: 0; + } + + .main-content.full-height.sidebar-mode { + height: 100vh; + } + .content-wrapper { max-width: 80rem; /* max-w-7xl */ margin-left: auto; diff --git a/apps/zitare/apps/web/src/routes/apps/+page.svelte b/apps/zitare/apps/web/src/routes/apps/+page.svelte new file mode 100644 index 000000000..2cac8cd64 --- /dev/null +++ b/apps/zitare/apps/web/src/routes/apps/+page.svelte @@ -0,0 +1,14 @@ + + +
+ +
+ + diff --git a/packages/shared-ui/package.json b/packages/shared-ui/package.json index 49bfb895b..5554a2ec1 100644 --- a/packages/shared-ui/package.json +++ b/packages/shared-ui/package.json @@ -32,6 +32,7 @@ "svelte": "^5.0.0" }, "dependencies": { + "@manacore/shared-branding": "workspace:*", "@manacore/shared-icons": "workspace:*" } } diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 8995b34f1..440c07c32 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -58,3 +58,6 @@ export { SettingsDangerZone, SettingsDangerButton, } from './settings'; + +// Pages +export { default as AppsPage } from './pages/AppsPage.svelte'; diff --git a/packages/shared-ui/src/navigation/PillNavigation.svelte b/packages/shared-ui/src/navigation/PillNavigation.svelte index 6ca62b864..b23ebe0cd 100644 --- a/packages/shared-ui/src/navigation/PillNavigation.svelte +++ b/packages/shared-ui/src/navigation/PillNavigation.svelte @@ -4,9 +4,13 @@ import PillDropdown from './PillDropdown.svelte'; import PillTabGroup from './PillTabGroup.svelte'; - // Convert app items to dropdown items - function appItemsToDropdownItems(apps: PillAppItem[]): PillDropdownItem[] { - return apps.map((app) => ({ + // Convert app items to dropdown items (will be computed as derived) + function createAppDropdownItems( + apps: PillAppItem[], + allAppsUrl?: string, + allAppsText?: string + ): PillDropdownItem[] { + const items: PillDropdownItem[] = apps.map((app) => ({ id: app.id, label: app.name, // Use image icon if available, otherwise use grid as fallback @@ -23,6 +27,24 @@ active: app.isCurrent, disabled: false, })); + + // Add "All Apps" link at the end if href is provided + if (allAppsUrl) { + items.push( + { id: 'all-apps-divider', label: '', divider: true }, + { + id: 'all-apps', + label: allAppsText || 'Alle Apps', + icon: 'grid', + onClick: () => { + window.location.href = allAppsUrl; + }, + active: false, + } + ); + } + + return items; } interface Props { @@ -86,6 +108,12 @@ manaHref?: string; /** Profile page href */ profileHref?: string; + /** Login page href (shown when not logged in) */ + loginHref?: string; + /** All Apps page href */ + allAppsHref?: string; + /** All Apps label (default: "Alle Apps") */ + allAppsLabel?: string; } let { @@ -119,6 +147,9 @@ settingsHref = '/settings', manaHref, profileHref, + loginHref, + allAppsHref, + allAppsLabel = 'Alle Apps', }: Props = $props(); // Type guards for elements @@ -245,7 +276,7 @@ {#if showAppSwitcher && appItems.length > 0} onLogout?.(), - danger: true, - }, + { id: 'auth-divider', label: '', divider: true }, + ...(showLogout && onLogout + ? [ + { + id: 'logout', + label: 'Logout', + icon: 'logout', + onClick: () => onLogout?.(), + danger: true, + }, + ] + : loginHref + ? [ + { + id: 'login', + label: 'Login', + icon: 'user', + onClick: () => { + window.location.href = loginHref; + }, + }, + ] + : []), ]} direction="down" label={truncateEmail(userEmail)} diff --git a/packages/shared-ui/src/pages/AppsPage.svelte b/packages/shared-ui/src/pages/AppsPage.svelte new file mode 100644 index 000000000..5f69fd0c1 --- /dev/null +++ b/packages/shared-ui/src/pages/AppsPage.svelte @@ -0,0 +1,616 @@ + + + + +
+

{title}

+ +
+ {#each apps as app, index} + + {/each} +
+
+ + +{#if selectedAppIndex !== null} + +{/if} + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4cfe945f..29762af48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2882,6 +2882,9 @@ importers: packages/shared-ui: dependencies: + '@manacore/shared-branding': + specifier: workspace:* + version: link:../shared-branding '@manacore/shared-icons': specifier: workspace:* version: link:../shared-icons