mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
feat(theme): add ThemePage components and distinct background colors
- Add unique background colors for each theme variant: - Lume: warm cream/gold tint - Nature: green tint in dark mode - Stone: blue-gray tint in dark mode - Ocean: blue tint in dark mode - Create shared-theme-ui components: - ThemeColorPreview: color circles preview component - ThemeCard: individual theme card with status support - ThemeGrid: responsive grid layout - ThemePage: full page component with mode selector - Integrate theme page in Chat app: - Add /themes route with ThemePage component - Add "🎨 Alle Themes" link to PillNavigation dropdown - Add palette icon to shared-ui icon set - Migrate Presi and Picture apps to shared-theme system - Update semantic color usage across all apps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
129692812b
commit
54383bf7c2
92 changed files with 1793 additions and 1936 deletions
|
|
@ -40,7 +40,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-4">
|
||||
<div class="border-t border-border bg-surface p-4">
|
||||
<div class="flex items-end gap-3 max-w-4xl mx-auto">
|
||||
<div class="flex-1 relative">
|
||||
<textarea
|
||||
|
|
@ -51,27 +51,27 @@
|
|||
{placeholder}
|
||||
{disabled}
|
||||
rows="1"
|
||||
class="w-full resize-none rounded-xl border border-gray-300 dark:border-gray-600
|
||||
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-gray-100
|
||||
class="w-full resize-none rounded-xl border border-border
|
||||
bg-muted text-foreground
|
||||
px-4 py-3 text-sm
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
placeholder:text-gray-500 dark:placeholder:text-gray-400"
|
||||
placeholder:text-muted-foreground"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
onclick={handleSubmit}
|
||||
disabled={disabled || !inputValue.trim()}
|
||||
aria-label="Nachricht senden"
|
||||
class="flex-shrink-0 p-3 rounded-xl bg-blue-600 text-white
|
||||
hover:bg-blue-700 active:bg-blue-800
|
||||
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600
|
||||
class="flex-shrink-0 p-3 rounded-xl bg-primary text-primary-foreground
|
||||
hover:bg-primary/90 active:bg-primary/80
|
||||
disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-primary
|
||||
transition-colors"
|
||||
>
|
||||
<PaperPlaneTilt size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 text-center mt-2">
|
||||
<p class="text-xs text-muted-foreground text-center mt-2">
|
||||
Enter zum Senden, Shift+Enter für neue Zeile
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@
|
|||
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- New Chat Button -->
|
||||
<div class="p-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="p-3 border-b border-border">
|
||||
<a
|
||||
href="/chat"
|
||||
class="flex items-center justify-center gap-2 w-full px-4 py-2.5
|
||||
bg-blue-600 hover:bg-blue-700 text-white rounded-lg
|
||||
bg-primary hover:bg-primary/90 text-primary-foreground rounded-lg
|
||||
font-medium transition-colors"
|
||||
>
|
||||
<Plus size={20} weight="bold" />
|
||||
|
|
@ -53,11 +53,11 @@
|
|||
{#if isLoading}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div
|
||||
class="animate-spin w-6 h-6 border-2 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-6 h-6 border-2 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if conversations.length === 0}
|
||||
<div class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
<div class="px-4 py-8 text-center text-muted-foreground">
|
||||
<p class="text-sm">Keine Konversationen</p>
|
||||
<p class="text-xs mt-1">Starte einen neuen Chat</p>
|
||||
</div>
|
||||
|
|
@ -69,14 +69,14 @@
|
|||
href="/chat/{conv.id}"
|
||||
class="block px-3 py-2 mx-2 rounded-lg transition-colors
|
||||
{isActive
|
||||
? 'bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300'}"
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'hover:bg-muted text-foreground'}"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-sm font-medium truncate">
|
||||
{truncateTitle(conv.title || 'Neue Konversation')}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-500 flex-shrink-0">
|
||||
<span class="text-xs text-muted-foreground flex-shrink-0">
|
||||
{formatDate(conv.updated_at || conv.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@
|
|||
<div class="flex {isUser ? 'justify-end' : 'justify-start'} mb-4">
|
||||
<div
|
||||
class="max-w-[80%] rounded-2xl px-4 py-3 {isUser
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 rounded-bl-md'}"
|
||||
? 'bg-primary text-primary-foreground rounded-br-md'
|
||||
: 'bg-muted text-foreground rounded-bl-md'}"
|
||||
>
|
||||
{#if isUser}
|
||||
<p class="whitespace-pre-wrap">{message.message_text}</p>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
{@html htmlContent}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="text-xs mt-1 {isUser ? 'text-blue-200' : 'text-gray-500 dark:text-gray-400'}">
|
||||
<div class="text-xs mt-1 {isUser ? 'text-primary-foreground/70' : 'text-muted-foreground'}">
|
||||
{formattedTime}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
<div bind:this={containerEl} class="flex-1 overflow-y-auto px-4 py-6">
|
||||
{#if messages.length === 0}
|
||||
<div class="flex flex-col items-center justify-center h-full text-gray-500 dark:text-gray-400">
|
||||
<div class="flex flex-col items-center justify-center h-full text-muted-foreground">
|
||||
<ChatCircleDots size={64} weight="light" class="mb-4 opacity-50" />
|
||||
<p class="text-lg font-medium">Keine Nachrichten</p>
|
||||
<p class="text-sm">Starte eine Konversation!</p>
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
value={selectedModelId}
|
||||
onchange={handleChange}
|
||||
{disabled}
|
||||
class="appearance-none bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100
|
||||
text-sm rounded-lg px-3 py-2 pr-8 border border-gray-200 dark:border-gray-700
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
class="appearance-none bg-muted text-foreground
|
||||
text-sm rounded-lg px-3 py-2 pr-8 border border-border
|
||||
focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
cursor-pointer min-w-[160px]"
|
||||
>
|
||||
|
|
@ -37,6 +37,6 @@
|
|||
{/if}
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||
<CaretDown size={16} weight="bold" class="text-gray-500" />
|
||||
<CaretDown size={16} weight="bold" class="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,18 +3,18 @@
|
|||
</script>
|
||||
|
||||
<div class="flex justify-start mb-4">
|
||||
<div class="bg-gray-100 dark:bg-gray-800 rounded-2xl rounded-bl-md px-4 py-3">
|
||||
<div class="bg-muted rounded-2xl rounded-bl-md px-4 py-3">
|
||||
<div class="flex items-center gap-1">
|
||||
<div
|
||||
class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"
|
||||
class="w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce"
|
||||
style="animation-delay: 0ms"
|
||||
></div>
|
||||
<div
|
||||
class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"
|
||||
class="w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce"
|
||||
style="animation-delay: 150ms"
|
||||
></div>
|
||||
<div
|
||||
class="w-2 h-2 bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce"
|
||||
class="w-2 h-2 bg-muted-foreground/50 rounded-full animate-bounce"
|
||||
style="animation-delay: 300ms"
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<svelte:window onclick={() => (showMenu = false)} />
|
||||
|
||||
<div
|
||||
class="group relative bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700
|
||||
class="group relative bg-surface rounded-xl border border-border
|
||||
shadow-sm hover:shadow-md transition-all cursor-pointer"
|
||||
onclick={() => onSelect(space.id)}
|
||||
onkeydown={(e) => e.key === 'Enter' && onSelect(space.id)}
|
||||
|
|
@ -47,13 +47,13 @@
|
|||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<UsersThree size={20} weight="bold" class="text-blue-500 flex-shrink-0" />
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white truncate">
|
||||
<UsersThree size={20} weight="bold" class="text-primary flex-shrink-0" />
|
||||
<h3 class="text-base font-semibold text-foreground truncate">
|
||||
{space.name}
|
||||
</h3>
|
||||
{#if isOwner}
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded"
|
||||
class="px-2 py-0.5 text-xs font-medium bg-primary/10 text-primary rounded"
|
||||
>
|
||||
Besitzer
|
||||
</span>
|
||||
|
|
@ -61,12 +61,12 @@
|
|||
</div>
|
||||
|
||||
{#if space.description}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 mb-2">
|
||||
<p class="text-sm text-muted-foreground line-clamp-2 mb-2">
|
||||
{space.description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<p class="text-xs text-gray-500 dark:text-gray-500">
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Erstellt: {formatDate(space.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -75,8 +75,8 @@
|
|||
<div class="relative">
|
||||
<button
|
||||
onclick={handleMenuClick}
|
||||
class="p-2 text-gray-500 hover:text-gray-700 dark:hover:text-gray-300
|
||||
hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
class="p-2 text-muted-foreground hover:text-foreground
|
||||
hover:bg-muted rounded-lg transition-colors"
|
||||
aria-label="Optionen"
|
||||
>
|
||||
<DotsThreeVertical size={20} weight="bold" />
|
||||
|
|
@ -84,8 +84,8 @@
|
|||
|
||||
{#if showMenu}
|
||||
<div
|
||||
class="absolute right-0 top-full mt-1 py-1 w-40 bg-white dark:bg-gray-800 rounded-lg shadow-lg
|
||||
border border-gray-200 dark:border-gray-700 z-10"
|
||||
class="absolute right-0 top-full mt-1 py-1 w-40 bg-surface rounded-lg shadow-lg
|
||||
border border-border z-10"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
onkeydown={() => {}}
|
||||
role="menu"
|
||||
|
|
@ -94,8 +94,8 @@
|
|||
{#if isOwner}
|
||||
<button
|
||||
onclick={() => handleAction(() => onEdit(space.id))}
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-300
|
||||
hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-foreground
|
||||
hover:bg-muted"
|
||||
role="menuitem"
|
||||
>
|
||||
<Gear size={16} weight="bold" />
|
||||
|
|
@ -103,8 +103,8 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={() => handleAction(() => onDelete(space.id))}
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-red-600 dark:text-red-400
|
||||
hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-destructive
|
||||
hover:bg-destructive/10"
|
||||
role="menuitem"
|
||||
>
|
||||
<Trash size={16} weight="bold" />
|
||||
|
|
@ -113,8 +113,8 @@
|
|||
{:else}
|
||||
<button
|
||||
onclick={() => handleAction(() => onLeave(space.id))}
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-red-600 dark:text-red-400
|
||||
hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 text-sm text-destructive
|
||||
hover:bg-destructive/10"
|
||||
role="menuitem"
|
||||
>
|
||||
<SignOut size={16} weight="bold" />
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-white dark:bg-gray-900 p-6 rounded-xl max-w-lg mx-auto">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
<div class="bg-surface p-6 rounded-xl max-w-lg mx-auto">
|
||||
<h2 class="text-xl font-bold text-foreground mb-6">
|
||||
{isEditMode ? 'Space bearbeiten' : 'Neuen Space erstellen'}
|
||||
</h2>
|
||||
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
>
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label for="name" class="block text-sm font-medium text-foreground mb-1">
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -59,13 +59,13 @@
|
|||
bind:value={name}
|
||||
maxlength={100}
|
||||
placeholder="Name des Spaces"
|
||||
class="w-full px-3 py-2 border rounded-lg bg-gray-50 dark:bg-gray-800
|
||||
text-gray-900 dark:text-white placeholder-gray-500
|
||||
{errors.name ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'}
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
class="w-full px-3 py-2 border rounded-lg bg-muted
|
||||
text-foreground placeholder-muted-foreground
|
||||
{errors.name ? 'border-destructive' : 'border-border'}
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
{#if errors.name}
|
||||
<p class="mt-1 text-sm text-red-500">{errors.name}</p>
|
||||
<p class="mt-1 text-sm text-destructive">{errors.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
<div>
|
||||
<label
|
||||
for="description"
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
class="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Beschreibung (optional)
|
||||
</label>
|
||||
|
|
@ -83,9 +83,9 @@
|
|||
maxlength={500}
|
||||
rows={3}
|
||||
placeholder="Worum geht es in diesem Space?"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||
class="w-full px-3 py-2 border border-border rounded-lg
|
||||
bg-muted text-foreground placeholder-muted-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
|
|
@ -94,15 +94,15 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={onCancel}
|
||||
class="flex-1 px-4 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300
|
||||
rounded-lg font-medium hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||
class="flex-1 px-4 py-2.5 border border-border text-foreground
|
||||
rounded-lg font-medium hover:bg-muted transition-colors"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 px-4 py-2.5 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors"
|
||||
class="flex-1 px-4 py-2.5 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
{isEditMode ? 'Speichern' : 'Erstellen'}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@
|
|||
</script>
|
||||
|
||||
<div
|
||||
class="group relative flex rounded-xl overflow-hidden bg-white dark:bg-gray-800 shadow-sm hover:shadow-md transition-all
|
||||
class="group relative flex rounded-xl overflow-hidden bg-surface shadow-sm hover:shadow-md transition-all
|
||||
{template.is_default
|
||||
? 'ring-2 ring-blue-500'
|
||||
: 'border border-gray-200 dark:border-gray-700'}"
|
||||
? 'ring-2 ring-primary'
|
||||
: 'border border-border'}"
|
||||
>
|
||||
<!-- Color Indicator -->
|
||||
<div class="w-2 flex-shrink-0" style="background-color: {template.color}"></div>
|
||||
|
|
@ -32,23 +32,23 @@
|
|||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white truncate">
|
||||
<h3 class="text-base font-semibold text-foreground truncate">
|
||||
{template.name}
|
||||
</h3>
|
||||
{#if template.is_default}
|
||||
<span class="px-2 py-0.5 text-xs font-medium bg-blue-500 text-white rounded">
|
||||
<span class="px-2 py-0.5 text-xs font-medium bg-primary text-primary-foreground rounded">
|
||||
Standard
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if template.description}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 mb-2">
|
||||
<p class="text-sm text-muted-foreground line-clamp-2 mb-2">
|
||||
{template.description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<p class="text-xs text-gray-500 dark:text-gray-500 italic line-clamp-2">
|
||||
<p class="text-xs text-muted-foreground italic line-clamp-2">
|
||||
{truncatePrompt(template.system_prompt)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
{#if !template.is_default}
|
||||
<button
|
||||
onclick={() => onSetDefault(template.id)}
|
||||
class="p-1.5 text-gray-500 hover:text-yellow-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
class="p-1.5 text-muted-foreground hover:text-yellow-500 hover:bg-muted rounded-lg transition-colors"
|
||||
title="Als Standard setzen"
|
||||
aria-label="Als Standard setzen"
|
||||
>
|
||||
|
|
@ -67,7 +67,7 @@
|
|||
{/if}
|
||||
<button
|
||||
onclick={() => onEdit(template.id)}
|
||||
class="p-1.5 text-gray-500 hover:text-blue-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
class="p-1.5 text-muted-foreground hover:text-primary hover:bg-muted rounded-lg transition-colors"
|
||||
title="Bearbeiten"
|
||||
aria-label="Bearbeiten"
|
||||
>
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={() => onDelete(template.id)}
|
||||
class="p-1.5 text-gray-500 hover:text-red-500 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||||
class="p-1.5 text-muted-foreground hover:text-destructive hover:bg-muted rounded-lg transition-colors"
|
||||
title="Löschen"
|
||||
aria-label="Löschen"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-white dark:bg-gray-900 p-6 rounded-xl max-w-2xl mx-auto">
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
<div class="bg-surface p-6 rounded-xl max-w-2xl mx-auto">
|
||||
<h2 class="text-xl font-bold text-foreground mb-6">
|
||||
{isEditMode ? 'Vorlage bearbeiten' : 'Neue Vorlage erstellen'}
|
||||
</h2>
|
||||
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
>
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label for="name" class="block text-sm font-medium text-foreground mb-1">
|
||||
Name *
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -99,13 +99,13 @@
|
|||
bind:value={name}
|
||||
maxlength={50}
|
||||
placeholder="Name der Vorlage"
|
||||
class="w-full px-3 py-2 border rounded-lg bg-gray-50 dark:bg-gray-800
|
||||
text-gray-900 dark:text-white placeholder-gray-500
|
||||
{errors.name ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'}
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
class="w-full px-3 py-2 border rounded-lg bg-muted
|
||||
text-foreground placeholder-muted-foreground
|
||||
{errors.name ? 'border-destructive' : 'border-border'}
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
{#if errors.name}
|
||||
<p class="mt-1 text-sm text-red-500">{errors.name}</p>
|
||||
<p class="mt-1 text-sm text-destructive">{errors.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
<div>
|
||||
<label
|
||||
for="description"
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
class="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Beschreibung (optional)
|
||||
</label>
|
||||
|
|
@ -123,9 +123,9 @@
|
|||
maxlength={200}
|
||||
rows={2}
|
||||
placeholder="Kurze Beschreibung dieser Vorlage"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||
class="w-full px-3 py-2 border border-border rounded-lg
|
||||
bg-muted text-foreground placeholder-muted-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
<div>
|
||||
<label
|
||||
for="systemPrompt"
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
class="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
System-Prompt *
|
||||
</label>
|
||||
|
|
@ -142,15 +142,15 @@
|
|||
bind:value={systemPrompt}
|
||||
rows={5}
|
||||
placeholder="System-Prompt für die KI"
|
||||
class="w-full px-3 py-2 border rounded-lg bg-gray-50 dark:bg-gray-800
|
||||
text-gray-900 dark:text-white placeholder-gray-500
|
||||
{errors.systemPrompt ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'}
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||
class="w-full px-3 py-2 border rounded-lg bg-muted
|
||||
text-foreground placeholder-muted-foreground
|
||||
{errors.systemPrompt ? 'border-destructive' : 'border-border'}
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
{#if errors.systemPrompt}
|
||||
<p class="mt-1 text-sm text-red-500">{errors.systemPrompt}</p>
|
||||
<p class="mt-1 text-sm text-destructive">{errors.systemPrompt}</p>
|
||||
{:else}
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Der System-Prompt definiert die Rolle und das Verhalten der KI.
|
||||
</p>
|
||||
{/if}
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
<div>
|
||||
<label
|
||||
for="initialQuestion"
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
class="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
Beispielfrage (optional)
|
||||
</label>
|
||||
|
|
@ -169,18 +169,18 @@
|
|||
bind:value={initialQuestion}
|
||||
rows={2}
|
||||
placeholder="Beispiel für eine passende Frage oder Anweisung"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
||||
class="w-full px-3 py-2 border border-border rounded-lg
|
||||
bg-muted text-foreground placeholder-muted-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Diese Frage wird als Vorschlag angezeigt, wenn die Vorlage ausgewählt wird.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Color -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> Farbe </label>
|
||||
<label class="block text-sm font-medium text-foreground mb-2"> Farbe </label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each TEMPLATE_COLORS as color}
|
||||
<button
|
||||
|
|
@ -210,22 +210,22 @@
|
|||
|
||||
<!-- Model -->
|
||||
<div>
|
||||
<label for="model" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
<label for="model" class="block text-sm font-medium text-foreground mb-1">
|
||||
Bevorzugtes Modell (optional)
|
||||
</label>
|
||||
<select
|
||||
id="model"
|
||||
bind:value={selectedModelId}
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
class="w-full px-3 py-2 border border-border rounded-lg
|
||||
bg-muted text-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
>
|
||||
<option value="">Kein Modell ausgewählt</option>
|
||||
{#each models as model}
|
||||
<option value={model.id}>{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-gray-500">
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Falls ausgewählt, wird dieses Modell automatisch mit der Vorlage verwendet.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -237,18 +237,18 @@
|
|||
onclick={() => (documentMode = !documentMode)}
|
||||
class="w-full flex items-center justify-between p-4 border rounded-lg transition-colors
|
||||
{documentMode
|
||||
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
|
||||
: 'border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800'}"
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border bg-muted'}"
|
||||
>
|
||||
<div class="text-left">
|
||||
<p class="font-medium text-gray-900 dark:text-white">Dokumentmodus aktivieren</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
<p class="font-medium text-foreground">Dokumentmodus aktivieren</p>
|
||||
<p class="text-xs text-muted-foreground mt-0.5">
|
||||
Ermöglicht die Bearbeitung eines Dokuments während der Konversation
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="w-6 h-6 rounded-full flex items-center justify-center
|
||||
{documentMode ? 'bg-blue-500' : 'bg-gray-400'}"
|
||||
{documentMode ? 'bg-primary' : 'bg-muted-foreground'}"
|
||||
>
|
||||
{#if documentMode}
|
||||
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -278,15 +278,15 @@
|
|||
<button
|
||||
type="button"
|
||||
onclick={onCancel}
|
||||
class="flex-1 px-4 py-2.5 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300
|
||||
rounded-lg font-medium hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||
class="flex-1 px-4 py-2.5 border border-border text-foreground
|
||||
rounded-lg font-medium hover:bg-muted transition-colors"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 px-4 py-2.5 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors"
|
||||
class="flex-1 px-4 py-2.5 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
{isEditMode ? 'Speichern' : 'Erstellen'}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@
|
|||
import { page } from '$app/stores';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
|
||||
import {
|
||||
isSidebarMode as sidebarModeStore,
|
||||
isNavCollapsed as collapsedStore,
|
||||
} from '$lib/stores/navigation';
|
||||
import { PillNavigation } from '@manacore/shared-ui';
|
||||
import type { PillNavItem } from '@manacore/shared-ui';
|
||||
import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui';
|
||||
import type { LayoutData } from './$types';
|
||||
|
||||
let { children, data }: { children: any; data: LayoutData } = $props();
|
||||
|
|
@ -21,6 +22,29 @@
|
|||
// Use theme store's isDark directly
|
||||
let isDark = $derived(theme.isDark);
|
||||
|
||||
// Theme variant dropdown items
|
||||
let themeVariantItems = $derived<PillDropdownItem[]>([
|
||||
// Theme variants
|
||||
...theme.variants.map((variant) => ({
|
||||
id: variant,
|
||||
label: `${THEME_DEFINITIONS[variant].emoji} ${THEME_DEFINITIONS[variant].label}`,
|
||||
onClick: () => theme.setVariant(variant),
|
||||
active: theme.variant === variant,
|
||||
})),
|
||||
// Separator and link to full themes page
|
||||
{
|
||||
id: 'all-themes',
|
||||
label: '🎨 Alle Themes',
|
||||
onClick: () => goto('/themes'),
|
||||
active: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// Current theme variant label
|
||||
let currentThemeVariantLabel = $derived(
|
||||
`${THEME_DEFINITIONS[theme.variant].emoji} ${THEME_DEFINITIONS[theme.variant].label}`
|
||||
);
|
||||
|
||||
// Navigation items for Chat
|
||||
const navItems: PillNavItem[] = [
|
||||
{ href: '/', label: 'Chat', icon: 'home' },
|
||||
|
|
@ -112,12 +136,12 @@
|
|||
|
||||
{#if isChecking}
|
||||
<!-- Loading state while checking auth -->
|
||||
<div class="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||
<div class="flex min-h-screen items-center justify-center bg-background">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-blue-500 border-r-transparent"
|
||||
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-primary border-r-transparent"
|
||||
></div>
|
||||
<p class="text-gray-500 dark:text-gray-400">Laden...</p>
|
||||
<p class="text-muted-foreground">Laden...</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -136,6 +160,9 @@
|
|||
{isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
showThemeVariants={true}
|
||||
{themeVariantItems}
|
||||
{currentThemeVariantLabel}
|
||||
showLanguageSwitcher={false}
|
||||
showLogout={true}
|
||||
onLogout={handleLogout}
|
||||
|
|
@ -144,7 +171,7 @@
|
|||
|
||||
<!-- Main Content with dynamic padding based on nav mode -->
|
||||
<main
|
||||
class="main-content bg-gray-50 dark:bg-gray-900"
|
||||
class="main-content bg-background"
|
||||
class:sidebar-mode={isSidebarMode && !isCollapsed}
|
||||
class:floating-mode={!isSidebarMode && !isCollapsed}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -53,12 +53,12 @@
|
|||
<title>Archiv | ManaChat</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-gray-50 dark:bg-gray-900 py-8">
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-background py-8">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Archiv</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
<h1 class="text-2xl font-bold text-foreground">Archiv</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
Deine archivierten Konversationen.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -67,14 +67,14 @@
|
|||
{#if isLoading}
|
||||
<div class="flex items-center justify-center py-16">
|
||||
<div
|
||||
class="animate-spin w-8 h-8 border-4 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-8 h-8 border-4 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if conversations.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="text-center py-16">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-400 mx-auto mb-4"
|
||||
class="w-16 h-16 text-muted-foreground mx-auto mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -86,24 +86,24 @@
|
|||
d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-1">
|
||||
<h3 class="text-lg font-medium text-foreground mb-1">
|
||||
Keine archivierten Konversationen
|
||||
</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400">Archivierte Gespräche erscheinen hier.</p>
|
||||
<p class="text-muted-foreground">Archivierte Gespräche erscheinen hier.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Conversation List -->
|
||||
<div class="space-y-3">
|
||||
{#each conversations as conv (conv.id)}
|
||||
<div
|
||||
class="group bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700
|
||||
class="group bg-surface rounded-xl border border-border
|
||||
shadow-sm hover:shadow-md transition-all overflow-hidden"
|
||||
>
|
||||
<button onclick={() => handleConversationClick(conv.id)} class="w-full p-4 text-left">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-400"
|
||||
class="w-5 h-5 text-muted-foreground"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -115,14 +115,14 @@
|
|||
d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">
|
||||
<h3 class="font-semibold text-foreground">
|
||||
{conv.title || 'Unbenannte Konversation'}
|
||||
</h3>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">{formatDate(conv.updated_at)}</span>
|
||||
<span class="text-xs text-muted-foreground">{formatDate(conv.updated_at)}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-xs text-gray-500">
|
||||
<span class="px-2 py-0.5 bg-gray-100 dark:bg-gray-700 rounded">
|
||||
<div class="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span class="px-2 py-0.5 bg-muted rounded">
|
||||
{conv.conversation_mode === 'free'
|
||||
? 'Freier Modus'
|
||||
: conv.conversation_mode === 'guided'
|
||||
|
|
@ -134,12 +134,12 @@
|
|||
|
||||
<!-- Actions -->
|
||||
<div
|
||||
class="flex justify-end gap-2 px-4 py-2 border-t border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50"
|
||||
class="flex justify-end gap-2 px-4 py-2 border-t border-border bg-muted/50"
|
||||
>
|
||||
<button
|
||||
onclick={() => handleUnarchive(conv.id)}
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400
|
||||
hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground
|
||||
hover:text-primary hover:bg-primary/10
|
||||
rounded-lg transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -154,8 +154,8 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={() => handleDelete(conv.id)}
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400
|
||||
hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm text-muted-foreground
|
||||
hover:text-destructive hover:bg-destructive/10
|
||||
rounded-lg transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@
|
|||
<!-- Sidebar Toggle (mobile) -->
|
||||
<button
|
||||
onclick={toggleSidebar}
|
||||
class="fixed bottom-4 left-4 z-50 p-3 bg-blue-600 text-white rounded-full shadow-lg
|
||||
sm:hidden hover:bg-blue-700 transition-colors"
|
||||
class="fixed bottom-4 left-4 z-50 p-3 bg-primary text-primary-foreground rounded-full shadow-lg
|
||||
sm:hidden hover:bg-primary/90 transition-colors"
|
||||
aria-label={showSidebar ? 'Seitenleiste schließen' : 'Seitenleiste öffnen'}
|
||||
>
|
||||
{#if showSidebar}
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
|
||||
<!-- Sidebar -->
|
||||
<aside
|
||||
class="w-72 flex-shrink-0 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700
|
||||
class="w-72 flex-shrink-0 bg-surface border-r border-border
|
||||
transition-transform duration-200 ease-in-out
|
||||
fixed sm:static inset-y-0 left-0 z-40 top-16
|
||||
{showSidebar
|
||||
|
|
|
|||
|
|
@ -138,11 +138,11 @@
|
|||
<div class="flex flex-col h-full">
|
||||
<!-- Chat Header -->
|
||||
<header
|
||||
class="flex-shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-3"
|
||||
class="flex-shrink-0 border-b border-border bg-surface px-4 py-3"
|
||||
>
|
||||
<div class="flex items-center justify-between max-w-4xl mx-auto">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Neuer Chat</h2>
|
||||
<h2 class="text-lg font-semibold text-foreground">Neuer Chat</h2>
|
||||
|
||||
<!-- Model Selector -->
|
||||
<ModelSelector
|
||||
|
|
@ -158,9 +158,9 @@
|
|||
onchange={handleTemplateSelect}
|
||||
value={selectedTemplateId}
|
||||
disabled={isSending}
|
||||
class="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
bg-white dark:bg-gray-800 text-gray-900 dark:text-white
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
class="px-3 py-1.5 text-sm border border-border rounded-lg
|
||||
bg-surface text-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent
|
||||
disabled:opacity-50"
|
||||
>
|
||||
<option value="">Ohne Vorlage</option>
|
||||
|
|
@ -179,8 +179,8 @@
|
|||
disabled={isSending}
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg transition-colors
|
||||
{documentMode
|
||||
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 border border-blue-300 dark:border-blue-700'
|
||||
: 'bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 border border-gray-300 dark:border-gray-600'}
|
||||
? 'bg-primary/10 text-primary border border-primary/30'
|
||||
: 'bg-muted text-muted-foreground border border-border'}
|
||||
hover:bg-opacity-80 disabled:opacity-50"
|
||||
title="Dokumentmodus aktivieren"
|
||||
>
|
||||
|
|
@ -192,9 +192,9 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="p-2 text-gray-700 dark:text-gray-300
|
||||
bg-gray-100 dark:bg-gray-800 rounded-lg
|
||||
hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||
class="p-2 text-muted-foreground
|
||||
bg-muted rounded-lg
|
||||
hover:bg-muted/80 transition-colors"
|
||||
aria-label="Theme wechseln"
|
||||
>
|
||||
<Moon size={20} weight="bold" />
|
||||
|
|
@ -204,7 +204,7 @@
|
|||
</header>
|
||||
|
||||
<!-- Messages Area -->
|
||||
<main class="flex-1 overflow-hidden bg-white dark:bg-gray-900">
|
||||
<main class="flex-1 overflow-hidden bg-surface">
|
||||
<div class="h-full max-w-4xl mx-auto flex flex-col">
|
||||
<MessageList {messages} isTyping={isSending} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -179,23 +179,23 @@
|
|||
{#if isLoading}
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div
|
||||
class="animate-spin w-8 h-8 border-4 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-8 h-8 border-4 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if error && !conversation}
|
||||
<div class="flex flex-col items-center justify-center h-full text-center p-4">
|
||||
<p class="text-red-500 mb-4">{error}</p>
|
||||
<a href="/chat" class="text-blue-600 hover:underline">Zurück zum Chat</a>
|
||||
<p class="text-destructive mb-4">{error}</p>
|
||||
<a href="/chat" class="text-primary hover:underline">Zurück zum Chat</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Chat Header -->
|
||||
<header
|
||||
class="flex-shrink-0 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-3"
|
||||
class="flex-shrink-0 border-b border-border bg-surface px-4 py-3"
|
||||
>
|
||||
<div class="flex items-center justify-between max-w-4xl mx-auto">
|
||||
<div class="flex items-center gap-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white truncate max-w-xs">
|
||||
<h2 class="text-lg font-semibold text-foreground truncate max-w-xs">
|
||||
{conversation?.title || 'Chat'}
|
||||
</h2>
|
||||
<ModelSelector
|
||||
|
|
@ -211,8 +211,8 @@
|
|||
onclick={toggleDocumentPanel}
|
||||
class="p-2 transition-colors rounded-lg
|
||||
{showDocumentPanel
|
||||
? 'text-blue-600 dark:text-blue-400 bg-blue-100 dark:bg-blue-900/30'
|
||||
: 'text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700'}"
|
||||
? 'text-primary bg-primary/10'
|
||||
: 'text-foreground bg-muted hover:bg-muted/80'}"
|
||||
aria-label="Dokument-Panel"
|
||||
title="Dokument-Panel ein/ausblenden"
|
||||
>
|
||||
|
|
@ -228,9 +228,7 @@
|
|||
{/if}
|
||||
<button
|
||||
onclick={handleArchive}
|
||||
class="p-2 text-gray-700 dark:text-gray-300
|
||||
bg-gray-100 dark:bg-gray-800 rounded-lg
|
||||
hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||
class="p-2 text-foreground bg-muted rounded-lg hover:bg-muted/80 transition-colors"
|
||||
aria-label="Archivieren"
|
||||
title="Archivieren"
|
||||
>
|
||||
|
|
@ -245,9 +243,7 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={handleDelete}
|
||||
class="p-2 text-red-600 dark:text-red-400
|
||||
bg-gray-100 dark:bg-gray-800 rounded-lg
|
||||
hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors"
|
||||
class="p-2 text-destructive bg-muted rounded-lg hover:bg-destructive/10 transition-colors"
|
||||
aria-label="Löschen"
|
||||
title="Löschen"
|
||||
>
|
||||
|
|
@ -262,9 +258,7 @@
|
|||
</button>
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="p-2 text-gray-700 dark:text-gray-300
|
||||
bg-gray-100 dark:bg-gray-800 rounded-lg
|
||||
hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
||||
class="p-2 text-foreground bg-muted rounded-lg hover:bg-muted/80 transition-colors"
|
||||
aria-label="Theme wechseln"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -289,7 +283,7 @@
|
|||
: 'w-full'}"
|
||||
>
|
||||
<!-- Messages Area -->
|
||||
<main class="flex-1 overflow-hidden bg-white dark:bg-gray-900">
|
||||
<main class="flex-1 overflow-hidden bg-surface">
|
||||
<div class="h-full max-w-4xl mx-auto flex flex-col">
|
||||
<MessageList {messages} isTyping={isSending} />
|
||||
</div>
|
||||
|
|
@ -302,15 +296,15 @@
|
|||
<!-- Document Panel -->
|
||||
{#if isDocumentMode && showDocumentPanel}
|
||||
<div
|
||||
class="hidden lg:flex lg:w-1/2 flex-col border-l border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900"
|
||||
class="hidden lg:flex lg:w-1/2 flex-col border-l border-border bg-surface"
|
||||
>
|
||||
<!-- Document Header -->
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-gray-700"
|
||||
class="flex items-center justify-between px-4 py-3 border-b border-border"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg
|
||||
class="w-5 h-5 text-blue-600 dark:text-blue-400"
|
||||
class="w-5 h-5 text-primary"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -322,10 +316,10 @@
|
|||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-medium text-gray-900 dark:text-white">Dokument</span>
|
||||
<span class="font-medium text-foreground">Dokument</span>
|
||||
{#if document}
|
||||
<span
|
||||
class="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded"
|
||||
class="text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded"
|
||||
>
|
||||
v{document.version}
|
||||
</span>
|
||||
|
|
@ -334,8 +328,8 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
onclick={loadVersions}
|
||||
class="p-1.5 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white
|
||||
hover:bg-gray-100 dark:hover:bg-gray-800 rounded transition-colors"
|
||||
class="p-1.5 text-muted-foreground hover:text-foreground
|
||||
hover:bg-muted rounded transition-colors"
|
||||
title="Versionen anzeigen"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -350,8 +344,8 @@
|
|||
<button
|
||||
onclick={saveDocument}
|
||||
disabled={isSavingDocument || !documentContent.trim()}
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white
|
||||
bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary-foreground
|
||||
bg-primary hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground
|
||||
rounded-lg transition-colors"
|
||||
>
|
||||
{#if isSavingDocument}
|
||||
|
|
@ -385,9 +379,9 @@ Du kannst Markdown verwenden:
|
|||
- Aufzählung
|
||||
**Fett** und *Kursiv*"
|
||||
class="w-full h-full min-h-[300px] p-4 text-sm font-mono
|
||||
bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white
|
||||
border border-gray-200 dark:border-gray-700 rounded-lg
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
||||
bg-muted text-foreground
|
||||
border border-border rounded-lg
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent
|
||||
resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
|
|
@ -398,7 +392,7 @@ Du kannst Markdown verwenden:
|
|||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<div
|
||||
class="fixed bottom-24 left-1/2 -translate-x-1/2 px-4 py-2 bg-red-500 text-white rounded-lg shadow-lg"
|
||||
class="fixed bottom-24 left-1/2 -translate-x-1/2 px-4 py-2 bg-destructive text-destructive-foreground rounded-lg shadow-lg"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
|
|
@ -409,15 +403,15 @@ Du kannst Markdown verwenden:
|
|||
{#if showVersionsModal}
|
||||
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-lg w-full max-h-[80vh] flex flex-col"
|
||||
class="bg-surface rounded-xl shadow-xl max-w-lg w-full max-h-[80vh] flex flex-col"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700"
|
||||
class="flex items-center justify-between p-4 border-b border-border"
|
||||
>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Dokumentversionen</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground">Dokumentversionen</h3>
|
||||
<button
|
||||
onclick={() => (showVersionsModal = false)}
|
||||
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
class="p-1 text-muted-foreground hover:text-foreground"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -432,7 +426,7 @@ Du kannst Markdown verwenden:
|
|||
</div>
|
||||
<div class="flex-1 overflow-auto p-4">
|
||||
{#if documentVersions.length === 0}
|
||||
<p class="text-center text-gray-500 dark:text-gray-400 py-8">
|
||||
<p class="text-center text-muted-foreground py-8">
|
||||
Keine Versionen vorhanden
|
||||
</p>
|
||||
{:else}
|
||||
|
|
@ -440,16 +434,16 @@ Du kannst Markdown verwenden:
|
|||
{#each documentVersions as version (version.id)}
|
||||
<button
|
||||
onclick={() => restoreVersion(version)}
|
||||
class="w-full p-3 text-left rounded-lg border border-gray-200 dark:border-gray-700
|
||||
hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors
|
||||
{version.id === document?.id ? 'ring-2 ring-blue-500' : ''}"
|
||||
class="w-full p-3 text-left rounded-lg border border-border
|
||||
hover:bg-muted transition-colors
|
||||
{version.id === document?.id ? 'ring-2 ring-primary' : ''}"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="font-medium text-gray-900 dark:text-white">
|
||||
<span class="font-medium text-foreground">
|
||||
Version {version.version}
|
||||
{version.id === document?.id ? ' (aktuell)' : ''}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{new Date(version.created_at).toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
|
|
@ -458,7 +452,7 @@ Du kannst Markdown verwenden:
|
|||
})}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2">
|
||||
<p class="text-sm text-muted-foreground line-clamp-2">
|
||||
{version.content.substring(0, 100)}...
|
||||
</p>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -77,20 +77,20 @@
|
|||
<title>Dokumente | ManaChat</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-gray-50 dark:bg-gray-900 py-8">
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-background py-8">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Dokumente</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
<h1 class="text-2xl font-bold text-foreground">Dokumente</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
Alle Dokumente aus deinen Konversationen im Dokumentmodus.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={loadDocuments}
|
||||
class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200
|
||||
hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
|
||||
class="p-2 text-muted-foreground hover:text-foreground
|
||||
hover:bg-muted rounded-lg transition-colors"
|
||||
aria-label="Aktualisieren"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -108,14 +108,14 @@
|
|||
{#if isLoading}
|
||||
<div class="flex items-center justify-center py-16">
|
||||
<div
|
||||
class="animate-spin w-8 h-8 border-4 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-8 h-8 border-4 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if documents.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="text-center py-16">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-400 mx-auto mb-4"
|
||||
class="w-16 h-16 text-muted-foreground mx-auto mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -127,10 +127,10 @@
|
|||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-1">
|
||||
<h3 class="text-lg font-medium text-foreground mb-1">
|
||||
Keine Dokumente gefunden
|
||||
</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 max-w-sm mx-auto">
|
||||
<p class="text-muted-foreground max-w-sm mx-auto">
|
||||
Erstelle ein neues Dokument in einer Konversation mit aktiviertem Dokumentmodus.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -140,19 +140,19 @@
|
|||
{#each documents as doc (doc.id)}
|
||||
<button
|
||||
onclick={() => navigateToConversation(doc.conversation_id)}
|
||||
class="text-left p-0 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700
|
||||
shadow-sm hover:shadow-md hover:border-blue-300 dark:hover:border-blue-600 transition-all overflow-hidden"
|
||||
class="text-left p-0 bg-surface rounded-xl border border-border
|
||||
shadow-sm hover:shadow-md hover:border-primary/50 transition-all overflow-hidden"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="p-4 border-b border-gray-100 dark:border-gray-700">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white line-clamp-2 mb-2">
|
||||
<div class="p-4 border-b border-border">
|
||||
<h3 class="font-semibold text-foreground line-clamp-2 mb-2">
|
||||
{extractTitle(doc.content)}
|
||||
</h3>
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<div class="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span class="truncate">{doc.conversation_title}</span>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<span>{formatDate(doc.updated_at)}</span>
|
||||
<span class="px-1.5 py-0.5 bg-gray-100 dark:bg-gray-700 rounded font-medium">
|
||||
<span class="px-1.5 py-0.5 bg-muted rounded font-medium">
|
||||
v{doc.version}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
|
||||
<!-- Preview -->
|
||||
<div class="p-4 h-32 overflow-hidden">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-5">
|
||||
<p class="text-sm text-muted-foreground line-clamp-5">
|
||||
{getPreview(doc.content)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,27 +26,27 @@
|
|||
<title>Profil | ManaChat</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-gray-50 dark:bg-gray-900 py-8">
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-background py-8">
|
||||
<div class="max-w-2xl mx-auto px-4">
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Profil</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
<h1 class="text-2xl font-bold text-foreground">Profil</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
Verwalte dein Konto und deine Einstellungen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Profile Card -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm overflow-hidden mb-6"
|
||||
class="bg-surface rounded-xl border border-border shadow-sm overflow-hidden mb-6"
|
||||
>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-4 mb-6">
|
||||
<div
|
||||
class="w-16 h-16 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center"
|
||||
class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 text-blue-600 dark:text-blue-400"
|
||||
class="w-8 h-8 text-primary"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -60,10 +60,10 @@
|
|||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
<h2 class="text-lg font-semibold text-foreground">
|
||||
{authStore.user?.email || 'Benutzer'}
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Mitglied seit {formatDate(authStore.user?.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -71,19 +71,19 @@
|
|||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
class="flex items-center justify-between py-3 border-b border-gray-100 dark:border-gray-700"
|
||||
class="flex items-center justify-between py-3 border-b border-border"
|
||||
>
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">E-Mail</p>
|
||||
<p class="text-sm text-gray-500">{authStore.user?.email || '-'}</p>
|
||||
<p class="font-medium text-foreground">E-Mail</p>
|
||||
<p class="text-sm text-muted-foreground">{authStore.user?.email || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-between py-3 border-b border-gray-100 dark:border-gray-700"
|
||||
class="flex items-center justify-between py-3 border-b border-border"
|
||||
>
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">Benutzer-ID</p>
|
||||
<p class="text-sm text-gray-500 font-mono">{authStore.user?.id || '-'}</p>
|
||||
<p class="font-medium text-foreground">Benutzer-ID</p>
|
||||
<p class="text-sm text-muted-foreground font-mono">{authStore.user?.id || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -92,22 +92,22 @@
|
|||
|
||||
<!-- Settings Card -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm overflow-hidden mb-6"
|
||||
class="bg-surface rounded-xl border border-border shadow-sm overflow-hidden mb-6"
|
||||
>
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Einstellungen</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground mb-4">Einstellungen</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Theme Toggle -->
|
||||
<div class="flex items-center justify-between py-3">
|
||||
<div>
|
||||
<p class="font-medium text-gray-900 dark:text-white">Dunkler Modus</p>
|
||||
<p class="text-sm text-gray-500">Aktiviere den dunklen Modus für die App</p>
|
||||
<p class="font-medium text-foreground">Dunkler Modus</p>
|
||||
<p class="text-sm text-muted-foreground">Aktiviere den dunklen Modus für die App</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="relative w-12 h-6 rounded-full transition-colors
|
||||
{theme.mode === 'dark' ? 'bg-blue-600' : 'bg-gray-300'}"
|
||||
{theme.mode === 'dark' ? 'bg-primary' : 'bg-muted'}"
|
||||
role="switch"
|
||||
aria-checked={theme.mode === 'dark'}
|
||||
aria-label="Dunkler Modus umschalten"
|
||||
|
|
@ -124,14 +124,14 @@
|
|||
|
||||
<!-- Sign Out -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 shadow-sm overflow-hidden"
|
||||
class="bg-surface rounded-xl border border-border shadow-sm overflow-hidden"
|
||||
>
|
||||
<div class="p-6">
|
||||
<button
|
||||
onclick={handleSignOut}
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-3 bg-red-50 dark:bg-red-900/20
|
||||
text-red-600 dark:text-red-400 rounded-lg font-medium
|
||||
hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors"
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-3 bg-destructive/10
|
||||
text-destructive rounded-lg font-medium
|
||||
hover:bg-destructive/20 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
|
|
|||
|
|
@ -86,20 +86,20 @@
|
|||
<title>Spaces | ManaChat</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-gray-50 dark:bg-gray-900 py-8">
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-background py-8">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Spaces</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
<h1 class="text-2xl font-bold text-foreground">Spaces</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
Organisiere deine Konversationen in kollaborativen Arbeitsbereichen.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={handleCreateNew}
|
||||
class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors"
|
||||
class="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
|
@ -117,14 +117,14 @@
|
|||
{#if spacesStore.isLoading}
|
||||
<div class="flex items-center justify-center py-16">
|
||||
<div
|
||||
class="animate-spin w-8 h-8 border-4 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-8 h-8 border-4 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if spacesStore.spaces.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="text-center py-16">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-400 mx-auto mb-4"
|
||||
class="w-16 h-16 text-muted-foreground mx-auto mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -136,16 +136,16 @@
|
|||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-1">
|
||||
<h3 class="text-lg font-medium text-foreground mb-1">
|
||||
Keine Spaces gefunden
|
||||
</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">
|
||||
<p class="text-muted-foreground mb-4">
|
||||
Erstelle einen neuen Space oder frage nach einer Einladung
|
||||
</p>
|
||||
<button
|
||||
onclick={handleCreateNew}
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
|
||||
<!-- Error Message -->
|
||||
{#if spacesStore.error}
|
||||
<div class="mt-4 p-4 bg-red-100 dark:bg-red-900/20 text-red-700 dark:text-red-400 rounded-lg">
|
||||
<div class="mt-4 p-4 bg-destructive/10 text-destructive rounded-lg">
|
||||
{spacesStore.error}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -87,23 +87,23 @@
|
|||
{#if isLoading}
|
||||
<div class="flex items-center justify-center h-[calc(100vh-4rem)]">
|
||||
<div
|
||||
class="animate-spin w-8 h-8 border-4 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-8 h-8 border-4 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="flex flex-col items-center justify-center h-[calc(100vh-4rem)] text-center p-4">
|
||||
<p class="text-red-500 mb-4">{error}</p>
|
||||
<a href="/spaces" class="text-blue-600 hover:underline">Zurück zu Spaces</a>
|
||||
<p class="text-destructive mb-4">{error}</p>
|
||||
<a href="/spaces" class="text-primary hover:underline">Zurück zu Spaces</a>
|
||||
</div>
|
||||
{:else if space}
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-gray-50 dark:bg-gray-900 py-8">
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-background py-8">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<a
|
||||
href="/spaces"
|
||||
class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||
class="p-1 text-muted-foreground hover:text-foreground rounded-lg hover:bg-muted transition-colors"
|
||||
aria-label="Zurück zu Spaces"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
|
@ -115,24 +115,24 @@
|
|||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{space.name}</h1>
|
||||
<h1 class="text-2xl font-bold text-foreground">{space.name}</h1>
|
||||
</div>
|
||||
{#if space.description}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{space.description}</p>
|
||||
<p class="text-sm text-muted-foreground">{space.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- New Chat Section -->
|
||||
<div
|
||||
class="mb-8 p-4 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700"
|
||||
class="mb-8 p-4 bg-surface rounded-xl border border-border"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-3">Neuen Chat starten</h2>
|
||||
<h2 class="text-lg font-semibold text-foreground mb-3">Neuen Chat starten</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<select
|
||||
bind:value={selectedModelId}
|
||||
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg
|
||||
bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-white
|
||||
focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
class="flex-1 px-3 py-2 border border-border rounded-lg
|
||||
bg-muted text-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
>
|
||||
{#each models as model}
|
||||
<option value={model.id}>{model.name}</option>
|
||||
|
|
@ -140,8 +140,8 @@
|
|||
</select>
|
||||
<button
|
||||
onclick={handleNewChat}
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors whitespace-nowrap"
|
||||
class="px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors whitespace-nowrap"
|
||||
>
|
||||
Chat starten
|
||||
</button>
|
||||
|
|
@ -150,16 +150,16 @@
|
|||
|
||||
<!-- Conversations List -->
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<h2 class="text-lg font-semibold text-foreground mb-4">
|
||||
Konversationen in diesem Space
|
||||
</h2>
|
||||
|
||||
{#if conversations.length === 0}
|
||||
<div
|
||||
class="text-center py-12 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700"
|
||||
class="text-center py-12 bg-surface rounded-xl border border-border"
|
||||
>
|
||||
<svg
|
||||
class="w-12 h-12 text-gray-400 mx-auto mb-3"
|
||||
class="w-12 h-12 text-muted-foreground mx-auto mb-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -171,7 +171,7 @@
|
|||
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
|
||||
/>
|
||||
</svg>
|
||||
<p class="text-gray-500 dark:text-gray-400">
|
||||
<p class="text-muted-foreground">
|
||||
Noch keine Konversationen in diesem Space.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -180,14 +180,14 @@
|
|||
{#each conversations as conv (conv.id)}
|
||||
<a
|
||||
href="/chat/{conv.id}"
|
||||
class="block p-4 bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700
|
||||
hover:border-blue-300 dark:hover:border-blue-600 transition-colors"
|
||||
class="block p-4 bg-surface rounded-xl border border-border
|
||||
hover:border-primary/50 transition-colors"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="font-medium text-gray-900 dark:text-white">
|
||||
<h3 class="font-medium text-foreground">
|
||||
{conv.title || 'Neue Konversation'}
|
||||
</h3>
|
||||
<span class="text-xs text-gray-500">{formatDate(conv.updated_at)}</span>
|
||||
<span class="text-xs text-muted-foreground">{formatDate(conv.updated_at)}</span>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -105,21 +105,21 @@
|
|||
<title>Vorlagen | ManaChat</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-gray-50 dark:bg-gray-900 py-8">
|
||||
<div class="min-h-[calc(100vh-4rem)] bg-background py-8">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Vorlagen</h1>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
<h1 class="text-2xl font-bold text-foreground">Vorlagen</h1>
|
||||
<p class="text-sm text-muted-foreground mt-1">
|
||||
Erstelle Vorlagen mit benutzerdefinierten System-Prompts für verschiedene
|
||||
KI-Verhaltensweisen.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onclick={handleCreateNew}
|
||||
class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors"
|
||||
class="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
|
@ -137,14 +137,14 @@
|
|||
{#if templatesStore.isLoading}
|
||||
<div class="flex items-center justify-center py-16">
|
||||
<div
|
||||
class="animate-spin w-8 h-8 border-4 border-blue-500 border-r-transparent rounded-full"
|
||||
class="animate-spin w-8 h-8 border-4 border-primary border-r-transparent rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
{:else if templatesStore.templates.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="text-center py-16">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-400 mx-auto mb-4"
|
||||
class="w-16 h-16 text-muted-foreground mx-auto mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
@ -156,16 +156,16 @@
|
|||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-1">
|
||||
<h3 class="text-lg font-medium text-foreground mb-1">
|
||||
Keine Vorlagen vorhanden
|
||||
</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">
|
||||
<p class="text-muted-foreground mb-4">
|
||||
Erstelle deine erste Vorlage, um loszulegen
|
||||
</p>
|
||||
<button
|
||||
onclick={handleCreateNew}
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium
|
||||
hover:bg-blue-700 transition-colors"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium
|
||||
hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
|
|
@ -195,7 +195,7 @@
|
|||
|
||||
<!-- Error Message -->
|
||||
{#if templatesStore.error}
|
||||
<div class="mt-4 p-4 bg-red-100 dark:bg-red-900/20 text-red-700 dark:text-red-400 rounded-lg">
|
||||
<div class="mt-4 p-4 bg-destructive/10 text-destructive rounded-lg">
|
||||
{templatesStore.error}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { ThemePage } from '@manacore/shared-theme-ui';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Themes | ManaChat</title>
|
||||
</svelte:head>
|
||||
|
||||
<ThemePage
|
||||
currentVariant={theme.variant}
|
||||
onSelectTheme={(v) => theme.setVariant(v)}
|
||||
showModeSelector={true}
|
||||
currentMode={theme.mode}
|
||||
onModeChange={(m) => theme.setMode(m)}
|
||||
showBackButton={true}
|
||||
onBack={() => goto('/chat')}
|
||||
/>
|
||||
|
|
@ -11,6 +11,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
<div class="min-h-screen bg-background text-foreground">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
<div class="flex items-center justify-center min-h-screen">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="animate-spin w-10 h-10 border-4 border-blue-500 border-r-transparent rounded-full mx-auto"
|
||||
class="animate-spin w-10 h-10 border-4 border-primary border-r-transparent rounded-full mx-auto"
|
||||
></div>
|
||||
<p class="mt-4 text-gray-600 dark:text-gray-400">Wird geladen...</p>
|
||||
<p class="mt-4 text-muted-foreground">Wird geladen...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@
|
|||
"@manacore/shared-auth-ui": "workspace:*",
|
||||
"@manacore/shared-branding": "workspace:*",
|
||||
"@manacore/shared-i18n": "workspace:*",
|
||||
"@manacore/shared-icons": "workspace:*",
|
||||
"@manacore/shared-subscription-types": "workspace:*",
|
||||
"@manacore/shared-subscription-ui": "workspace:*",
|
||||
"@manacore/shared-tailwind": "workspace:*",
|
||||
"@manacore/shared-theme": "workspace:*",
|
||||
"@manacore/shared-theme-ui": "workspace:*",
|
||||
"@manacore/shared-ui": "workspace:*",
|
||||
"@picture/design-tokens": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { AppSlider, type AppItem } from '@manacore/shared-ui';
|
||||
import { APP_ICONS } from '@manacore/shared-branding';
|
||||
import { actualMode } from '$lib/stores/theme';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
let isDark = $derived($actualMode === 'dark');
|
||||
|
||||
let apps = $derived<AppItem[]>([
|
||||
{
|
||||
name: 'Picture',
|
||||
|
|
@ -78,7 +76,7 @@
|
|||
<AppSlider
|
||||
{apps}
|
||||
title={$t('app_slider.title')}
|
||||
{isDark}
|
||||
isDark={theme.isDark}
|
||||
{statusLabels}
|
||||
comingSoonLabel={$t('app_slider.coming_soon')}
|
||||
openAppLabel={$t('app_slider.download')}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
import { locale } from 'svelte-i18n';
|
||||
import { LanguageSelector } from '@manacore/shared-i18n';
|
||||
import { setLocale, supportedLocales } from '$lib/i18n';
|
||||
import { actualMode } from '$lib/stores/theme';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
|
||||
let isDark = $derived($actualMode === 'dark');
|
||||
let currentLocale = $derived($locale || 'de');
|
||||
|
||||
function handleLocaleChange(newLocale: string) {
|
||||
|
|
@ -16,6 +15,6 @@
|
|||
{currentLocale}
|
||||
{supportedLocales}
|
||||
onLocaleChange={handleLocaleChange}
|
||||
{isDark}
|
||||
isDark={theme.isDark}
|
||||
primaryColor="#3b82f6"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Image } from '$lib/api/images';
|
||||
import { showContextMenu } from '$lib/stores/contextMenu';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
|
||||
interface Props {
|
||||
image: Image;
|
||||
onclick: () => void;
|
||||
|
|
@ -38,7 +36,7 @@
|
|||
type="button"
|
||||
>
|
||||
<img
|
||||
src={image.public_url}
|
||||
src={image.publicUrl}
|
||||
alt={image.prompt}
|
||||
class="h-full w-full object-cover transition-opacity duration-300 {imageLoaded
|
||||
? 'opacity-100'
|
||||
|
|
@ -52,7 +50,7 @@
|
|||
class="absolute inset-0 flex flex-col justify-end bg-gradient-to-t from-black/80 via-black/40 to-transparent p-4 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
<p class="text-base font-medium text-white">{image.prompt}</p>
|
||||
<p class="mt-1 text-sm text-gray-300">{formatDate(image.created_at)}</p>
|
||||
<p class="mt-1 text-sm text-gray-300">{formatDate(image.createdAt)}</p>
|
||||
</div>
|
||||
|
||||
<!-- Archived badge - always visible -->
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
<script lang="ts">
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Image } from '$lib/api/images';
|
||||
import Modal from '../ui/Modal.svelte';
|
||||
import Button from '../ui/Button.svelte';
|
||||
import { unarchiveImage, deleteImage, downloadImage } from '$lib/api/images';
|
||||
import { archivedImages } from '$lib/stores/archive';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
import { DownloadSimple, ArrowCounterClockwise, Trash } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
image: Image | null;
|
||||
|
|
@ -54,9 +53,9 @@
|
|||
}
|
||||
|
||||
function handleDownload() {
|
||||
if (!image) return;
|
||||
if (!image || !image.publicUrl) return;
|
||||
const filename = `picture-${image.id}.png`;
|
||||
downloadImage(image.public_url, filename);
|
||||
downloadImage(image.publicUrl, filename);
|
||||
}
|
||||
|
||||
function formatDate(dateString: string) {
|
||||
|
|
@ -77,7 +76,7 @@
|
|||
<!-- Image -->
|
||||
<div class="flex-1">
|
||||
<img
|
||||
src={image.public_url}
|
||||
src={image.publicUrl}
|
||||
alt={image.prompt}
|
||||
class="h-auto w-full rounded-lg object-contain"
|
||||
/>
|
||||
|
|
@ -101,26 +100,19 @@
|
|||
<!-- Model -->
|
||||
<div>
|
||||
<h3 class="mb-2 text-sm font-medium text-gray-500">Model</h3>
|
||||
<p class="text-gray-900">{image.model_id || 'Unknown'}</p>
|
||||
<p class="text-gray-900">{image.model || 'Unknown'}</p>
|
||||
</div>
|
||||
|
||||
<!-- Created At -->
|
||||
<div>
|
||||
<h3 class="mb-2 text-sm font-medium text-gray-500">Created</h3>
|
||||
<p class="text-gray-900">{formatDate(image.created_at)}</p>
|
||||
<p class="text-gray-900">{formatDate(image.createdAt)}</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="space-y-2">
|
||||
<Button variant="primary" class="w-full" onclick={handleDownload}>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
<DownloadSimple size={20} class="mr-2" />
|
||||
Download
|
||||
</Button>
|
||||
|
||||
|
|
@ -131,14 +123,7 @@
|
|||
loading={isUnarchiving}
|
||||
disabled={isUnarchiving || isDeleting}
|
||||
>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowCounterClockwise size={20} class="mr-2" />
|
||||
Restore to Gallery
|
||||
</Button>
|
||||
|
||||
|
|
@ -149,14 +134,7 @@
|
|||
loading={isDeleting}
|
||||
disabled={isUnarchiving || isDeleting}
|
||||
>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<Trash size={20} class="mr-2" />
|
||||
Delete Permanently
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -226,13 +226,13 @@
|
|||
const konvaImage = new Konva.Image({
|
||||
id: item.id,
|
||||
name: item.id,
|
||||
x: item.position_x,
|
||||
y: item.position_y,
|
||||
x: item.positionX,
|
||||
y: item.positionY,
|
||||
image: imageObj,
|
||||
width: item.width || imageObj.width,
|
||||
height: item.height || imageObj.height,
|
||||
scaleX: item.scale_x,
|
||||
scaleY: item.scale_y,
|
||||
scaleX: item.scaleX,
|
||||
scaleY: item.scaleY,
|
||||
rotation: item.rotation,
|
||||
draggable: true,
|
||||
opacity: item.opacity,
|
||||
|
|
@ -273,9 +273,9 @@
|
|||
const konvaText = new Konva.Text({
|
||||
id: item.id,
|
||||
name: item.id,
|
||||
x: item.position_x,
|
||||
y: item.position_y,
|
||||
text: item.text_content,
|
||||
x: item.positionX,
|
||||
y: item.positionY,
|
||||
text: item.textContent,
|
||||
fontSize: item.font_size,
|
||||
fontFamily: item.properties?.fontFamily || 'Arial',
|
||||
fontStyle: `${item.properties?.fontStyle || 'normal'} ${item.properties?.fontWeight || 'normal'}`,
|
||||
|
|
@ -283,8 +283,8 @@
|
|||
width: item.width || 300,
|
||||
align: item.properties?.textAlign || 'left',
|
||||
rotation: item.rotation,
|
||||
scaleX: item.scale_x,
|
||||
scaleY: item.scale_y,
|
||||
scaleX: item.scaleX,
|
||||
scaleY: item.scaleY,
|
||||
opacity: item.opacity,
|
||||
draggable: true,
|
||||
lineHeight: item.properties?.lineHeight || 1.2,
|
||||
|
|
@ -364,12 +364,12 @@
|
|||
|
||||
// Update store
|
||||
updateCanvasItem(itemId, {
|
||||
position_x: x,
|
||||
position_y: y,
|
||||
positionX: x,
|
||||
positionY: y,
|
||||
});
|
||||
|
||||
// Save to database
|
||||
await saveBoardItem(itemId, { position_x: x, position_y: y });
|
||||
await saveBoardItem(itemId, { positionX: x, positionY: y });
|
||||
}
|
||||
|
||||
async function handleTransformEnd(node: Konva.Image, itemId: string) {
|
||||
|
|
@ -379,15 +379,15 @@
|
|||
|
||||
// Update store
|
||||
updateCanvasItem(itemId, {
|
||||
scale_x: scaleX,
|
||||
scale_y: scaleY,
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
rotation: rotation,
|
||||
});
|
||||
|
||||
// Save to database
|
||||
await saveBoardItem(itemId, {
|
||||
scale_x: scaleX,
|
||||
scale_y: scaleY,
|
||||
scaleX: scaleX,
|
||||
scaleY: scaleY,
|
||||
rotation: rotation,
|
||||
});
|
||||
}
|
||||
|
|
@ -405,16 +405,16 @@
|
|||
// Update store
|
||||
updateCanvasItem(itemId, {
|
||||
width: Math.round(width),
|
||||
scale_x: 1,
|
||||
scale_y: scaleY,
|
||||
scaleX: 1,
|
||||
scaleY: scaleY,
|
||||
rotation: rotation,
|
||||
});
|
||||
|
||||
// Save to database
|
||||
await saveBoardItem(itemId, {
|
||||
width: Math.round(width),
|
||||
scale_x: 1,
|
||||
scale_y: scaleY,
|
||||
scaleX: 1,
|
||||
scaleY: scaleY,
|
||||
rotation: rotation,
|
||||
});
|
||||
}
|
||||
|
|
@ -459,8 +459,8 @@
|
|||
layer.batchDraw();
|
||||
|
||||
// Update store and database
|
||||
updateCanvasItem(item.id, { text_content: newText });
|
||||
await saveBoardItem(item.id, { text_content: newText });
|
||||
updateCanvasItem(item.id, { textContent: newText });
|
||||
await saveBoardItem(item.id, { textContent: newText });
|
||||
stopEditingText();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,19 @@
|
|||
import { boardSettings } from '$lib/stores/boards';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import Konva from 'konva';
|
||||
import {
|
||||
CaretLeft,
|
||||
ArrowCounterClockwise,
|
||||
ArrowClockwise,
|
||||
Minus,
|
||||
Plus,
|
||||
GridFour,
|
||||
Table,
|
||||
Trash,
|
||||
TextT,
|
||||
Image,
|
||||
DownloadSimple,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
boardName: string;
|
||||
|
|
@ -80,14 +93,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
title="Zurück"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretLeft size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
<div>
|
||||
|
|
@ -107,14 +113,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 disabled:opacity-30 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
title="Rückgängig (⌘Z)"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowCounterClockwise size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -123,14 +122,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 disabled:opacity-30 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
title="Wiederherstellen (⌘⇧Z)"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 10h-10a8 8 0 00-8 8v2m18-10l-6 6m6-6l-6-6"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowClockwise size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
<div class="mx-2 h-6 w-px bg-gray-300 dark:bg-gray-600"></div>
|
||||
|
|
@ -141,9 +133,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
title="Verkleinern"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
|
||||
</svg>
|
||||
<Minus size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
<div
|
||||
|
|
@ -157,14 +147,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-lg text-gray-600 transition-colors hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
title="Vergrößern"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
<Plus size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -193,14 +176,7 @@
|
|||
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800'}"
|
||||
title="Raster {$showGrid ? 'ausblenden' : 'einblenden'}"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"
|
||||
/>
|
||||
</svg>
|
||||
<GridFour size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
<!-- Snap Toggle -->
|
||||
|
|
@ -211,14 +187,7 @@
|
|||
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800'}"
|
||||
title="Am Raster einrasten"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||
/>
|
||||
</svg>
|
||||
<Table size={20} weight="bold" />
|
||||
</button>
|
||||
|
||||
{#if hasSelection}
|
||||
|
|
@ -230,14 +199,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-lg text-red-600 transition-colors hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950"
|
||||
title="Löschen (Entf)"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<Trash size={20} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -245,38 +207,17 @@
|
|||
<!-- Right: Actions -->
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" onclick={onAddText}>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h7"
|
||||
/>
|
||||
</svg>
|
||||
<TextT size={20} class="mr-2" />
|
||||
Text
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onclick={onAddImages}>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<Image size={20} class="mr-2" />
|
||||
Bilder
|
||||
</Button>
|
||||
|
||||
<Button variant="secondary" onclick={handleExport}>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
<DownloadSimple size={20} class="mr-2" />
|
||||
Exportieren
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { images, isLoading as isLoadingImages } from '$lib/stores/images';
|
||||
import { canvasItems, addCanvasItem } from '$lib/stores/canvas';
|
||||
import { getImages } from '$lib/api/images';
|
||||
import { addBoardItem, isImageOnBoard } from '$lib/api/boardItems';
|
||||
import { addBoardItem } from '$lib/api/boardItems';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import Modal from '$lib/components/ui/Modal.svelte';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import { MagnifyingGlass, Image as ImageIcon, Check } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -18,8 +17,6 @@
|
|||
|
||||
let { open, onClose }: Props = $props();
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
|
||||
let selectedImages = $state<Set<string>>(new Set());
|
||||
let isAdding = $state(false);
|
||||
let searchQuery = $state('');
|
||||
|
|
@ -41,7 +38,6 @@
|
|||
isLoadingImages.set(true);
|
||||
try {
|
||||
const data = await getImages({
|
||||
userId: authStore.user.id,
|
||||
page: 1,
|
||||
limit: 50,
|
||||
archived: false,
|
||||
|
|
@ -72,7 +68,7 @@
|
|||
}
|
||||
|
||||
function isImageAlreadyOnBoard(imageId: string): boolean {
|
||||
return $canvasItems.some((item) => item.image_id === imageId);
|
||||
return $canvasItems.some((item) => item.imageId === imageId);
|
||||
}
|
||||
|
||||
async function handleAddImages() {
|
||||
|
|
@ -94,17 +90,11 @@
|
|||
|
||||
// Add to board
|
||||
const boardItem = await addBoardItem({
|
||||
board_id: boardId,
|
||||
image_id: imageId,
|
||||
position_x: 100 + addedCount * 20, // Offset each image slightly
|
||||
position_y: 100 + addedCount * 20,
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
rotation: 0,
|
||||
z_index: addedCount,
|
||||
opacity: 1,
|
||||
width: image.width || 400,
|
||||
height: image.height || 300,
|
||||
boardId: boardId,
|
||||
itemType: 'image',
|
||||
imageId: imageId,
|
||||
positionX: 100 + addedCount * 20, // Offset each image slightly
|
||||
positionY: 100 + addedCount * 20,
|
||||
});
|
||||
|
||||
// Add to canvas
|
||||
|
|
@ -160,19 +150,7 @@
|
|||
placeholder="Bilder suchen..."
|
||||
class="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 pl-10 text-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
|
||||
/>
|
||||
<svg
|
||||
class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<MagnifyingGlass size={20} class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
|
|
@ -204,19 +182,7 @@
|
|||
</div>
|
||||
{:else if filteredImages.length === 0}
|
||||
<div class="flex h-full flex-col items-center justify-center py-12">
|
||||
<svg
|
||||
class="h-16 w-16 text-gray-300 dark:text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<ImageIcon size={64} weight="thin" class="text-gray-300 dark:text-gray-600" />
|
||||
<p class="mt-4 text-gray-600 dark:text-gray-400">
|
||||
{searchQuery ? 'Keine Bilder gefunden' : 'Keine Bilder in deiner Galerie'}
|
||||
</p>
|
||||
|
|
@ -236,7 +202,7 @@
|
|||
: 'hover:ring-2 hover:ring-gray-300'}"
|
||||
>
|
||||
<img
|
||||
src={image.public_url}
|
||||
src={image.publicUrl}
|
||||
alt={image.prompt || 'Image'}
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
|
|
@ -246,14 +212,7 @@
|
|||
<div
|
||||
class="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-blue-500 text-white"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<Check size={16} weight="bold" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
<script lang="ts">
|
||||
import { selectedItems, updateCanvasItem, removeSelectedItems } from '$lib/stores/canvas';
|
||||
import { updateBoardItem, changeBoardItemZIndex } from '$lib/api/boardItems';
|
||||
import { updateBoardItem, changeBoardItemZIndex, isImageItem } from '$lib/api/boardItems';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import {
|
||||
Image,
|
||||
CaretDoubleUp,
|
||||
CaretDoubleDown,
|
||||
ArrowUp,
|
||||
ArrowDown,
|
||||
Trash,
|
||||
CursorClick,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
const selectedItem = $derived($selectedItems[0] || null);
|
||||
const hasMultipleSelected = $derived($selectedItems.length > 1);
|
||||
const selectedImageItem = $derived(selectedItem && isImageItem(selectedItem) ? selectedItem : null);
|
||||
|
||||
// Local state for inputs (synced with selected item)
|
||||
let positionX = $state(0);
|
||||
|
|
@ -19,10 +29,10 @@
|
|||
// Update local state when selection changes
|
||||
$effect(() => {
|
||||
if (selectedItem) {
|
||||
positionX = Math.round(selectedItem.position_x);
|
||||
positionY = Math.round(selectedItem.position_y);
|
||||
scaleX = Math.round(selectedItem.scale_x * 100);
|
||||
scaleY = Math.round(selectedItem.scale_y * 100);
|
||||
positionX = Math.round(selectedItem.positionX);
|
||||
positionY = Math.round(selectedItem.positionY);
|
||||
scaleX = Math.round(selectedItem.scaleX * 100);
|
||||
scaleY = Math.round(selectedItem.scaleY * 100);
|
||||
rotation = Math.round(selectedItem.rotation);
|
||||
opacity = Math.round(selectedItem.opacity * 100);
|
||||
}
|
||||
|
|
@ -31,7 +41,7 @@
|
|||
async function handlePositionChange(axis: 'x' | 'y', value: number) {
|
||||
if (!selectedItem) return;
|
||||
|
||||
const updates = axis === 'x' ? { position_x: value } : { position_y: value };
|
||||
const updates = axis === 'x' ? { positionX: value } : { positionY: value };
|
||||
|
||||
updateCanvasItem(selectedItem.id, updates);
|
||||
|
||||
|
|
@ -50,11 +60,11 @@
|
|||
let updates: any = {};
|
||||
|
||||
if (lockAspectRatio) {
|
||||
updates = { scale_x: scale, scale_y: scale };
|
||||
updates = { scaleX: scale, scaleY: scale };
|
||||
scaleX = percent;
|
||||
scaleY = percent;
|
||||
} else {
|
||||
updates = axis === 'x' ? { scale_x: scale } : { scale_y: scale };
|
||||
updates = axis === 'x' ? { scaleX: scale } : { scaleY: scale };
|
||||
}
|
||||
|
||||
updateCanvasItem(selectedItem.id, updates);
|
||||
|
|
@ -137,19 +147,7 @@
|
|||
class="h-full overflow-y-auto border-l border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900"
|
||||
>
|
||||
<div class="text-center">
|
||||
<svg
|
||||
class="mx-auto h-12 w-12 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<Image size={48} weight="regular" class="mx-auto text-gray-400" />
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{$selectedItems.length} Bilder ausgewählt
|
||||
</h3>
|
||||
|
|
@ -170,22 +168,24 @@
|
|||
</h3>
|
||||
|
||||
<!-- Image Preview -->
|
||||
<div class="mb-6 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<img src={selectedItem.image.public_url} alt="Preview" class="w-full" />
|
||||
</div>
|
||||
|
||||
<!-- Prompt Info -->
|
||||
{#if selectedItem.image.prompt}
|
||||
<div class="mb-6">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Prompt
|
||||
</label>
|
||||
<p
|
||||
class="rounded-lg bg-gray-50 p-3 text-sm text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
{selectedItem.image.prompt}
|
||||
</p>
|
||||
{#if selectedImageItem?.image}
|
||||
<div class="mb-6 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<img src={selectedImageItem.image.publicUrl} alt="Preview" class="w-full" />
|
||||
</div>
|
||||
|
||||
<!-- Prompt Info -->
|
||||
{#if selectedImageItem.image.prompt}
|
||||
<div class="mb-6">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Prompt
|
||||
</label>
|
||||
<p
|
||||
class="rounded-lg bg-gray-50 p-3 text-sm text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
||||
>
|
||||
{selectedImageItem.image.prompt}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Position -->
|
||||
|
|
@ -339,56 +339,28 @@
|
|||
onclick={() => handleLayerChange('top')}
|
||||
class="flex items-center justify-center gap-2 rounded-lg border border-gray-300 px-3 py-2 text-sm hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 15l7-7 7 7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretDoubleUp size={16} />
|
||||
Nach vorne
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleLayerChange('bottom')}
|
||||
class="flex items-center justify-center gap-2 rounded-lg border border-gray-300 px-3 py-2 text-sm hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretDoubleDown size={16} />
|
||||
Nach hinten
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleLayerChange('up')}
|
||||
class="flex items-center justify-center gap-2 rounded-lg border border-gray-300 px-3 py-2 text-sm hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 10l7-7m0 0l7 7m-7-7v18"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowUp size={16} />
|
||||
Eine Ebene
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleLayerChange('down')}
|
||||
class="flex items-center justify-center gap-2 rounded-lg border border-gray-300 px-3 py-2 text-sm hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 14l-7 7m0 0l-7-7m7 7V3"
|
||||
/>
|
||||
</svg>
|
||||
<ArrowDown size={16} />
|
||||
Eine Ebene
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -397,22 +369,24 @@
|
|||
<!-- Dimensions Info -->
|
||||
<div class="mb-6 rounded-lg bg-gray-50 p-3 dark:bg-gray-800">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="flex justify-between py-1">
|
||||
<span>Original:</span>
|
||||
<span class="font-medium"
|
||||
>{selectedItem.image.width} × {selectedItem.image.height}px</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex justify-between py-1">
|
||||
<span>Aktuell:</span>
|
||||
<span class="font-medium">
|
||||
{Math.round((selectedItem.image.width || 0) * selectedItem.scale_x)} ×
|
||||
{Math.round((selectedItem.image.height || 0) * selectedItem.scale_y)}px
|
||||
</span>
|
||||
</div>
|
||||
{#if selectedImageItem?.image}
|
||||
<div class="flex justify-between py-1">
|
||||
<span>Original:</span>
|
||||
<span class="font-medium"
|
||||
>{selectedImageItem.image.width} × {selectedImageItem.image.height}px</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex justify-between py-1">
|
||||
<span>Aktuell:</span>
|
||||
<span class="font-medium">
|
||||
{Math.round((selectedImageItem.image.width || 0) * selectedItem.scaleX)} ×
|
||||
{Math.round((selectedImageItem.image.height || 0) * selectedItem.scaleY)}px
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-between py-1">
|
||||
<span>Z-Index:</span>
|
||||
<span class="font-medium">{selectedItem.z_index}</span>
|
||||
<span class="font-medium">{selectedItem.zIndex}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -423,14 +397,7 @@
|
|||
Transform zurücksetzen
|
||||
</Button>
|
||||
<Button variant="danger" class="w-full" onclick={handleDelete}>
|
||||
<svg class="mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<Trash size={16} class="mr-2" />
|
||||
Bild entfernen
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -442,19 +409,7 @@
|
|||
class="flex h-full items-center justify-center border-l border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900"
|
||||
>
|
||||
<div class="text-center">
|
||||
<svg
|
||||
class="mx-auto h-12 w-12 text-gray-300 dark:text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122"
|
||||
/>
|
||||
</svg>
|
||||
<CursorClick size={48} weight="thin" class="mx-auto text-gray-300 dark:text-gray-600" />
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
Wähle ein Bild aus, um<br />seine Eigenschaften zu bearbeiten
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts">
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Image } from '$lib/api/images';
|
||||
import ImageCard from './ImageCard.svelte';
|
||||
import { selectedImage } from '$lib/stores/images';
|
||||
import { viewMode, type ViewMode } from '$lib/stores/view';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
import { Image as ImageIcon } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
images: Image[];
|
||||
|
|
@ -31,19 +30,7 @@
|
|||
{#if images.length === 0}
|
||||
<div class="flex min-h-[400px] items-center justify-center rounded-lg bg-white p-8 shadow">
|
||||
<div class="text-center">
|
||||
<svg
|
||||
class="mx-auto h-24 w-24 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<ImageIcon size={96} weight="thin" class="mx-auto text-gray-400" />
|
||||
<h2 class="mt-4 text-xl font-semibold text-gray-900">No images yet</h2>
|
||||
<p class="mt-2 text-gray-600">Start generating AI images to see them here.</p>
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Image } from '$lib/api/images';
|
||||
import type { ViewMode } from '$lib/stores/view';
|
||||
import { showContextMenu } from '$lib/stores/contextMenu';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
|
||||
interface Props {
|
||||
image: Image;
|
||||
onclick?: () => void;
|
||||
|
|
@ -49,7 +47,7 @@
|
|||
>
|
||||
<div class="w-full {aspectClass}">
|
||||
<img
|
||||
src={image.public_url}
|
||||
src={image.publicUrl}
|
||||
alt={image.prompt}
|
||||
class="h-full w-full object-cover transition-opacity duration-300 {imageLoaded
|
||||
? 'opacity-100'
|
||||
|
|
@ -68,13 +66,13 @@
|
|||
</p>
|
||||
{#if viewMode !== 'grid5'}
|
||||
<p class="text-sm text-white/80">
|
||||
{formatDate(image.created_at)}
|
||||
{formatDate(image.createdAt)}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if image.archived_at}
|
||||
{#if image.archivedAt}
|
||||
<div class="absolute right-2 top-2 rounded-full bg-black/50 px-2 py-1 text-xs text-white">
|
||||
Archived
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { Image } from '$lib/api/images';
|
||||
import type { Tag } from '$lib/api/tags';
|
||||
import {
|
||||
archiveImage,
|
||||
deleteImage,
|
||||
|
|
@ -11,9 +12,18 @@
|
|||
import { showToast } from '$lib/stores/toast';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import { getImageTags, getAllTags, addTagToImage, removeTagFromImage } from '$lib/api/tags';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
type Tag = Database['public']['Tables']['tags']['Row'];
|
||||
import {
|
||||
X,
|
||||
Info,
|
||||
Tag as TagIcon,
|
||||
DownloadSimple,
|
||||
Globe,
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
Archive,
|
||||
Trash,
|
||||
Check,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
image: Image | null;
|
||||
|
|
@ -33,7 +43,7 @@
|
|||
let isPublishing = $state(false);
|
||||
|
||||
// Get current image index
|
||||
const currentIndex = $derived(image ? $images.findIndex((img) => img.id === image.id) : -1);
|
||||
const currentIndex = $derived(image ? $images.findIndex((img) => img.id === image?.id) : -1);
|
||||
|
||||
const hasPrevious = $derived(currentIndex > 0);
|
||||
const hasNext = $derived(currentIndex >= 0 && currentIndex < $images.length - 1);
|
||||
|
|
@ -87,12 +97,13 @@
|
|||
|
||||
async function handleArchive() {
|
||||
if (!image) return;
|
||||
const imageId = image.id;
|
||||
|
||||
isArchiving = true;
|
||||
try {
|
||||
await archiveImage(image.id);
|
||||
await archiveImage(imageId);
|
||||
// Update store
|
||||
images.update((current) => current.filter((img) => img.id !== image.id));
|
||||
images.update((current) => current.filter((img) => img.id !== imageId));
|
||||
showToast('Bild erfolgreich archiviert', 'success');
|
||||
onClose();
|
||||
} catch (error) {
|
||||
|
|
@ -105,6 +116,7 @@
|
|||
|
||||
async function handleDelete() {
|
||||
if (!image) return;
|
||||
const imageId = image.id;
|
||||
if (
|
||||
!confirm(
|
||||
'Bist du sicher, dass du dieses Bild löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.'
|
||||
|
|
@ -114,9 +126,9 @@
|
|||
|
||||
isDeleting = true;
|
||||
try {
|
||||
await deleteImage(image.id);
|
||||
await deleteImage(imageId);
|
||||
// Update store
|
||||
images.update((current) => current.filter((img) => img.id !== image.id));
|
||||
images.update((current) => current.filter((img) => img.id !== imageId));
|
||||
showToast('Bild erfolgreich gelöscht', 'success');
|
||||
onClose();
|
||||
} catch (error) {
|
||||
|
|
@ -128,9 +140,9 @@
|
|||
}
|
||||
|
||||
function handleDownload() {
|
||||
if (!image) return;
|
||||
if (!image || !image.publicUrl) return;
|
||||
const filename = `picture-${image.id}.png`;
|
||||
downloadImage(image.public_url, filename);
|
||||
downloadImage(image.publicUrl, filename);
|
||||
showToast('Download gestartet', 'success');
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +211,7 @@
|
|||
await publishImage(image.id);
|
||||
// Update local image state
|
||||
if (image) {
|
||||
image = { ...image, is_public: true };
|
||||
image = { ...image, isPublic: true };
|
||||
}
|
||||
showToast('Bild erfolgreich veröffentlicht!', 'success');
|
||||
closePublishModal();
|
||||
|
|
@ -219,7 +231,7 @@
|
|||
await unpublishImage(image.id);
|
||||
// Update local image state
|
||||
if (image) {
|
||||
image = { ...image, is_public: false };
|
||||
image = { ...image, isPublic: false };
|
||||
}
|
||||
showToast('Bild nicht mehr öffentlich', 'success');
|
||||
closePublishModal();
|
||||
|
|
@ -249,14 +261,7 @@
|
|||
class="fixed right-4 top-4 z-[60] flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-xl transition-all hover:bg-white/20"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={24} weight="bold" />
|
||||
</button>
|
||||
|
||||
<!-- Info Toggle -->
|
||||
|
|
@ -270,14 +275,7 @@
|
|||
: ''}"
|
||||
aria-label="Info anzeigen"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Info size={24} />
|
||||
</button>
|
||||
|
||||
<!-- Tags Button -->
|
||||
|
|
@ -289,14 +287,7 @@
|
|||
class="fixed right-4 top-36 z-[60] flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-xl transition-all hover:bg-white/20"
|
||||
aria-label="Tags verwalten"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
|
||||
/>
|
||||
</svg>
|
||||
<TagIcon size={24} />
|
||||
</button>
|
||||
|
||||
<!-- Download Button -->
|
||||
|
|
@ -308,14 +299,7 @@
|
|||
class="fixed right-4 top-52 z-[60] flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-xl transition-all hover:bg-white/20"
|
||||
aria-label="Download"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
<DownloadSimple size={24} />
|
||||
</button>
|
||||
|
||||
<!-- Publish Button -->
|
||||
|
|
@ -324,19 +308,12 @@
|
|||
e.stopPropagation();
|
||||
openPublishModal();
|
||||
}}
|
||||
class="fixed right-4 top-[17rem] z-[60] flex h-12 w-12 items-center justify-center rounded-full transition-all {image?.is_public
|
||||
class="fixed right-4 top-[17rem] z-[60] flex h-12 w-12 items-center justify-center rounded-full transition-all {image?.isPublic
|
||||
? 'bg-green-500/20 text-green-400 hover:bg-green-500/30'
|
||||
: 'bg-white/10 text-white hover:bg-white/20'} backdrop-blur-xl"
|
||||
aria-label="Veröffentlichen"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Globe size={24} />
|
||||
</button>
|
||||
|
||||
<!-- Main Image Container -->
|
||||
|
|
@ -351,20 +328,13 @@
|
|||
class="absolute left-4 top-1/2 z-[60] flex h-14 w-14 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-xl transition-all hover:bg-white/20"
|
||||
aria-label="Vorheriges Bild"
|
||||
>
|
||||
<svg class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretLeft size={28} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<!-- Image -->
|
||||
<img
|
||||
src={image.public_url}
|
||||
src={image.publicUrl}
|
||||
alt={image.prompt}
|
||||
class="max-h-full max-w-full object-contain"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
|
|
@ -380,14 +350,7 @@
|
|||
class="absolute right-4 top-1/2 z-[60] flex h-14 w-14 -translate-y-1/2 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-xl transition-all hover:bg-white/20"
|
||||
aria-label="Nächstes Bild"
|
||||
>
|
||||
<svg class="h-7 w-7" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretRight size={28} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -423,7 +386,7 @@
|
|||
<h3 class="mb-1 text-xs font-semibold uppercase tracking-wide text-white/60">
|
||||
Model
|
||||
</h3>
|
||||
<p class="text-sm text-white">{image.model_id || 'Unknown'}</p>
|
||||
<p class="text-sm text-white">{image.model || 'Unknown'}</p>
|
||||
</div>
|
||||
|
||||
{#if imageTags.length > 0}
|
||||
|
|
@ -456,7 +419,7 @@
|
|||
<h3 class="mb-1 text-xs font-semibold uppercase tracking-wide text-white/60">
|
||||
Erstellt
|
||||
</h3>
|
||||
<p class="text-sm text-white">{formatDate(image.created_at)}</p>
|
||||
<p class="text-sm text-white">{formatDate(image.createdAt)}</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
|
|
@ -465,14 +428,7 @@
|
|||
onclick={handleDownload}
|
||||
class="flex flex-1 items-center justify-center gap-2 rounded-lg bg-white/20 px-4 py-2.5 text-sm font-medium text-white backdrop-blur-xl transition-all hover:bg-white/30"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
<DownloadSimple size={16} />
|
||||
Download
|
||||
</button>
|
||||
|
||||
|
|
@ -481,14 +437,7 @@
|
|||
disabled={isArchiving || isDeleting}
|
||||
class="flex items-center justify-center gap-2 rounded-lg bg-white/20 px-4 py-2.5 text-sm font-medium text-white backdrop-blur-xl transition-all hover:bg-white/30 disabled:opacity-50"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
|
||||
/>
|
||||
</svg>
|
||||
<Archive size={16} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
@ -496,14 +445,7 @@
|
|||
disabled={isArchiving || isDeleting}
|
||||
class="flex items-center justify-center gap-2 rounded-lg bg-red-500/20 px-4 py-2.5 text-sm font-medium text-white backdrop-blur-xl transition-all hover:bg-red-500/30 disabled:opacity-50"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<Trash size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -535,14 +477,7 @@
|
|||
class="flex h-8 w-8 items-center justify-center rounded-full text-gray-500 transition-colors hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -578,19 +513,7 @@
|
|||
</span>
|
||||
</div>
|
||||
{#if isSelected}
|
||||
<svg
|
||||
class="h-5 w-5 text-blue-600 dark:text-blue-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<Check size={20} weight="bold" class="text-blue-600 dark:text-blue-400" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
|
@ -625,25 +548,18 @@
|
|||
>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{image.is_public ? 'Veröffentlichung entfernen' : 'Bild veröffentlichen'}
|
||||
{image.isPublic ? 'Veröffentlichung entfernen' : 'Bild veröffentlichen'}
|
||||
</h2>
|
||||
<button
|
||||
onclick={closePublishModal}
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full text-gray-500 transition-colors hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if image.is_public}
|
||||
{#if image.isPublic}
|
||||
<div class="mb-6">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Dieses Bild ist derzeit öffentlich und kann von anderen Nutzern im Explore-Bereich
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
type AdvancedSettings,
|
||||
type AspectRatio,
|
||||
} from '$lib/components/generate/AdvancedSettingsModal.svelte';
|
||||
import { Gear, Lightning, X } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
onGenerated?: () => void;
|
||||
|
|
@ -36,8 +37,8 @@
|
|||
$effect(() => {
|
||||
if ($selectedModel) {
|
||||
// Update defaults from model
|
||||
advancedSettings.steps = $selectedModel.default_steps || 50;
|
||||
advancedSettings.guidanceScale = parseFloat($selectedModel.default_guidance_scale) || 7.5;
|
||||
advancedSettings.steps = $selectedModel.defaultSteps || 50;
|
||||
advancedSettings.guidanceScale = $selectedModel.defaultGuidanceScale || 7.5;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -51,7 +52,7 @@
|
|||
const data = await getActiveModels();
|
||||
models.set(data);
|
||||
// Select default model
|
||||
const defaultModel = data.find((m) => m.is_default) || data[0];
|
||||
const defaultModel = data.find((m) => m.isDefault) || data[0];
|
||||
if (defaultModel) {
|
||||
selectedModelId = defaultModel.id;
|
||||
selectedModel.set(defaultModel);
|
||||
|
|
@ -87,11 +88,11 @@
|
|||
// Start generation with new async API
|
||||
const { generationId } = await generateImageAsync({
|
||||
prompt: prompt.trim(),
|
||||
model_id: selectedModelId,
|
||||
modelId: selectedModelId,
|
||||
width: advancedSettings.aspectRatio.width,
|
||||
height: advancedSettings.aspectRatio.height,
|
||||
num_inference_steps: advancedSettings.steps,
|
||||
guidance_scale: advancedSettings.guidanceScale,
|
||||
numInferenceSteps: advancedSettings.steps,
|
||||
guidanceScale: advancedSettings.guidanceScale,
|
||||
});
|
||||
|
||||
// Wait for completion using realtime subscription
|
||||
|
|
@ -222,7 +223,7 @@
|
|||
{#each $models as model}
|
||||
<option value={model.id}>
|
||||
{model.name}
|
||||
{model.is_default ? '(Standard)' : ''}
|
||||
{model.isDefault ? '(Standard)' : ''}
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
|
|
@ -260,20 +261,7 @@
|
|||
class="relative flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-gray-100/80 text-gray-600 backdrop-blur-xl transition-all hover:bg-gray-200/80 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-800/80 dark:text-gray-400 dark:hover:bg-gray-700/80"
|
||||
aria-label="Einstellungen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Gear size={20} />
|
||||
{#if hasCustomSettings}
|
||||
<span class="absolute right-0 top-0 flex h-3 w-3">
|
||||
<span
|
||||
|
|
@ -300,19 +288,7 @@
|
|||
class="h-5 w-5 animate-spin rounded-full border-2 border-solid border-white border-r-transparent"
|
||||
></div>
|
||||
{:else}
|
||||
<svg
|
||||
class="h-5 w-5 text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<Lightning size={20} weight="fill" class="text-white" />
|
||||
<span class="text-sm font-medium text-white">Generieren</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -324,14 +300,7 @@
|
|||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-gray-100/80 text-gray-600 backdrop-blur-xl transition-all hover:bg-gray-200/80 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-800/80 dark:text-gray-400 dark:hover:bg-gray-700/80"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -346,14 +315,7 @@
|
|||
class="flex h-14 items-center justify-center gap-2 rounded-full bg-blue-600/90 px-6 text-white shadow-2xl backdrop-blur-xl transition-all hover:scale-105 hover:bg-blue-700/90 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-blue-500/90 dark:hover:bg-blue-600/90"
|
||||
aria-label="Generieren"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<Lightning size={24} weight="fill" />
|
||||
<span class="text-sm font-medium">Bild generieren</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -370,14 +332,7 @@
|
|||
class="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-600/90 text-white backdrop-blur-xl transition-all hover:bg-blue-700/90 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-blue-500/90 dark:hover:bg-blue-600/90"
|
||||
aria-label="Erweitern"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<Lightning size={24} weight="fill" />
|
||||
</button>
|
||||
<div class="flex-1 text-sm text-gray-600 dark:text-gray-400">Bild generieren...</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { fly, fade } from 'svelte/transition';
|
||||
import { X } from '@manacore/shared-icons';
|
||||
|
||||
export interface AspectRatio {
|
||||
label: string;
|
||||
|
|
@ -80,14 +81,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-full bg-gray-100 text-gray-600 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import { generateImageAsync, subscribeToGenerationUpdates } from '$lib/api/generate-async';
|
||||
import Button from '../ui/Button.svelte';
|
||||
import Card from '../ui/Card.svelte';
|
||||
import { XCircle, Lightning } from '@manacore/shared-icons';
|
||||
|
||||
let prompt = $state('');
|
||||
let negativePrompt = $state('');
|
||||
|
|
@ -26,7 +27,7 @@
|
|||
const data = await getActiveModels();
|
||||
models.set(data);
|
||||
// Select default model
|
||||
const defaultModel = data.find((m) => m.is_default) || data[0];
|
||||
const defaultModel = data.find((m) => m.isDefault) || data[0];
|
||||
if (defaultModel) {
|
||||
selectedModelId = defaultModel.id;
|
||||
selectedModel.set(defaultModel);
|
||||
|
|
@ -55,8 +56,8 @@
|
|||
// Start generation with new async API
|
||||
const { generationId } = await generateImageAsync({
|
||||
prompt: prompt.trim(),
|
||||
model_id: selectedModelId,
|
||||
negative_prompt: negativePrompt.trim() || undefined,
|
||||
modelId: selectedModelId,
|
||||
negativePrompt: negativePrompt.trim() || undefined,
|
||||
});
|
||||
|
||||
// Wait for completion using realtime subscription
|
||||
|
|
@ -113,13 +114,7 @@
|
|||
<div class="rounded-md bg-red-50 p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<XCircle size={20} weight="fill" class="text-red-400" />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-red-800">{$generationError}</p>
|
||||
|
|
@ -159,7 +154,7 @@
|
|||
{#each $models as model}
|
||||
<option value={model.id}>
|
||||
{model.name}
|
||||
{model.is_default ? '(Default)' : ''}
|
||||
{model.isDefault ? '(Default)' : ''}
|
||||
</option>
|
||||
{/each}
|
||||
{/if}
|
||||
|
|
@ -223,14 +218,7 @@
|
|||
disabled={!canGenerate}
|
||||
loading={$isGenerating}
|
||||
>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<Lightning size={20} class="mr-2" />
|
||||
{$isGenerating ? 'Generating...' : 'Generate Image'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { List, X, CaretDown } from '@manacore/shared-icons';
|
||||
|
||||
let showUserMenu = $state(false);
|
||||
let showMobileMenu = $state(false);
|
||||
|
|
@ -30,14 +31,11 @@
|
|||
class="flex items-center justify-center rounded-lg p-2 text-gray-700 hover:bg-gray-100 md:hidden dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
aria-label="Menu"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d={showMobileMenu ? 'M6 18L18 6M6 6l12 12' : 'M4 6h16M4 12h16M4 18h16'}
|
||||
/>
|
||||
</svg>
|
||||
{#if showMobileMenu}
|
||||
<X size={24} weight="bold" />
|
||||
{:else}
|
||||
<List size={24} weight="bold" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
|
|
@ -87,14 +85,7 @@
|
|||
>
|
||||
{authStore.user?.email?.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretDown size={16} />
|
||||
</button>
|
||||
|
||||
{#if showUserMenu}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { currentTheme } from '$lib/stores/theme';
|
||||
import { viewMode, cycleViewMode, type ViewMode } from '$lib/stores/view';
|
||||
import { isSidebarCollapsed, setSidebarCollapsed } from '$lib/stores/sidebar';
|
||||
import {
|
||||
|
|
@ -19,6 +18,24 @@
|
|||
import { searchPublicImages, getPublicImages } from '$lib/api/explore';
|
||||
import { showKeyboardShortcuts } from '$lib/stores/ui';
|
||||
import TagPills from '$lib/components/tags/TagPills.svelte';
|
||||
import {
|
||||
List,
|
||||
Image,
|
||||
SquaresFour,
|
||||
Square,
|
||||
MagnifyingGlass,
|
||||
Lightning,
|
||||
CloudArrowUp,
|
||||
Tag,
|
||||
Archive,
|
||||
CurrencyCircleDollar,
|
||||
Question,
|
||||
CaretLeft,
|
||||
CaretDown,
|
||||
User,
|
||||
SignOut,
|
||||
Heart,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
let showUserMenu = $state(false);
|
||||
let searchInput = $state('');
|
||||
|
|
@ -33,16 +50,15 @@
|
|||
return $page.url.pathname === path;
|
||||
}
|
||||
|
||||
function getViewModeIcon(mode: ViewMode) {
|
||||
switch (mode) {
|
||||
case 'single':
|
||||
return 'M4 6h16M4 12h16M4 18h16';
|
||||
case 'grid3':
|
||||
return 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z';
|
||||
case 'grid5':
|
||||
return 'M4 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM10 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM16 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5z';
|
||||
}
|
||||
}
|
||||
type IconName =
|
||||
| 'gallery'
|
||||
| 'board'
|
||||
| 'explore'
|
||||
| 'generate'
|
||||
| 'upload'
|
||||
| 'tags'
|
||||
| 'archive'
|
||||
| 'subscription';
|
||||
|
||||
function handleSearchInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
|
@ -103,50 +119,18 @@
|
|||
interface NavItem {
|
||||
path: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
iconName: IconName;
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
path: '/app/gallery',
|
||||
label: 'Galerie',
|
||||
icon: 'M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z',
|
||||
},
|
||||
{
|
||||
path: '/app/board',
|
||||
label: 'Moodboards',
|
||||
icon: 'M4 5a1 1 0 011-1h4a1 1 0 011 1v7a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v7a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 16a1 1 0 011-1h4a1 1 0 011 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-3zM14 16a1 1 0 011-1h4a1 1 0 011 1v3a1 1 0 01-1 1h-4a1 1 0 01-1-1v-3z',
|
||||
},
|
||||
{
|
||||
path: '/app/explore',
|
||||
label: 'Entdecken',
|
||||
icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
|
||||
},
|
||||
{
|
||||
path: '/app/generate',
|
||||
label: 'Generieren',
|
||||
icon: 'M13 10V3L4 14h7v7l9-11h-7z',
|
||||
},
|
||||
{
|
||||
path: '/app/upload',
|
||||
label: 'Upload',
|
||||
icon: 'M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12',
|
||||
},
|
||||
{
|
||||
path: '/app/tags',
|
||||
label: 'Tags',
|
||||
icon: 'M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z',
|
||||
},
|
||||
{
|
||||
path: '/app/archive',
|
||||
label: 'Archiv',
|
||||
icon: 'M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4',
|
||||
},
|
||||
{
|
||||
path: '/app/subscription',
|
||||
label: 'Abonnement',
|
||||
icon: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
},
|
||||
{ path: '/app/gallery', label: 'Galerie', iconName: 'gallery' },
|
||||
{ path: '/app/board', label: 'Moodboards', iconName: 'board' },
|
||||
{ path: '/app/explore', label: 'Entdecken', iconName: 'explore' },
|
||||
{ path: '/app/generate', label: 'Generieren', iconName: 'generate' },
|
||||
{ path: '/app/upload', label: 'Upload', iconName: 'upload' },
|
||||
{ path: '/app/tags', label: 'Tags', iconName: 'tags' },
|
||||
{ path: '/app/archive', label: 'Archiv', iconName: 'archive' },
|
||||
{ path: '/app/subscription', label: 'Abonnement', iconName: 'subscription' },
|
||||
];
|
||||
</script>
|
||||
|
||||
|
|
@ -157,14 +141,7 @@
|
|||
class:-translate-x-[calc(100%+2rem)]={!$isSidebarCollapsed}
|
||||
aria-label="Sidebar öffnen"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
<List size={24} weight="bold" />
|
||||
</button>
|
||||
|
||||
<!-- Sidebar for Desktop -->
|
||||
|
|
@ -182,9 +159,7 @@
|
|||
class="flex h-8 w-8 items-center justify-center rounded-lg text-gray-400 backdrop-blur-xl transition-colors hover:bg-gray-100/80 hover:text-gray-600 dark:hover:bg-gray-800/80 dark:hover:text-gray-300"
|
||||
aria-label="Sidebar schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<CaretLeft size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -198,16 +173,29 @@
|
|||
? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400'
|
||||
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'}"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 {active
|
||||
<span
|
||||
class="{active
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-400 group-hover:text-gray-600 dark:text-gray-500 dark:group-hover:text-gray-300'}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={item.icon} />
|
||||
</svg>
|
||||
{#if item.iconName === 'gallery'}
|
||||
<Image size={20} />
|
||||
{:else if item.iconName === 'board'}
|
||||
<SquaresFour size={20} />
|
||||
{:else if item.iconName === 'explore'}
|
||||
<MagnifyingGlass size={20} />
|
||||
{:else if item.iconName === 'generate'}
|
||||
<Lightning size={20} />
|
||||
{:else if item.iconName === 'upload'}
|
||||
<CloudArrowUp size={20} />
|
||||
{:else if item.iconName === 'tags'}
|
||||
<Tag size={20} />
|
||||
{:else if item.iconName === 'archive'}
|
||||
<Archive size={20} />
|
||||
{:else if item.iconName === 'subscription'}
|
||||
<CurrencyCircleDollar size={20} />
|
||||
{/if}
|
||||
</span>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
|
|
@ -220,19 +208,9 @@
|
|||
onclick={() => showKeyboardShortcuts.set(true)}
|
||||
class="group flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-gray-700 transition-all hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-gray-400 group-hover:text-gray-600 dark:text-gray-500 dark:group-hover:text-gray-300"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-gray-400 group-hover:text-gray-600 dark:text-gray-500 dark:group-hover:text-gray-300">
|
||||
<Question size={20} />
|
||||
</span>
|
||||
<span>Tastaturkürzel</span>
|
||||
</button>
|
||||
|
||||
|
|
@ -255,9 +233,7 @@
|
|||
: 'text-gray-400 hover:bg-gray-100/80 hover:text-gray-600 dark:text-gray-500 dark:hover:bg-gray-800/80 dark:hover:text-gray-300'}"
|
||||
title="Liste"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
<List size={20} weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => viewMode.set('grid3')}
|
||||
|
|
@ -267,11 +243,7 @@
|
|||
: 'text-gray-400 hover:bg-gray-100/80 hover:text-gray-600 dark:text-gray-500 dark:hover:bg-gray-800/80 dark:hover:text-gray-300'}"
|
||||
title="Mittel"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"
|
||||
/>
|
||||
</svg>
|
||||
<SquaresFour size={20} weight="bold" />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => viewMode.set('grid5')}
|
||||
|
|
@ -281,11 +253,7 @@
|
|||
: 'text-gray-400 hover:bg-gray-100/80 hover:text-gray-600 dark:text-gray-500 dark:hover:bg-gray-800/80 dark:hover:text-gray-300'}"
|
||||
title="Klein"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M4 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM10 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM16 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM4 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1v-2zM10 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2zM16 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2z"
|
||||
/>
|
||||
</svg>
|
||||
<Square size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -319,19 +287,7 @@
|
|||
? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'}"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill={$showExploreFavoritesOnly ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Heart size={16} weight={$showExploreFavoritesOnly ? 'fill' : 'regular'} />
|
||||
<span>Favoriten</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -350,19 +306,9 @@
|
|||
placeholder="Prompts..."
|
||||
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 pl-9 text-sm text-gray-900 placeholder-gray-500 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 dark:placeholder-gray-400"
|
||||
/>
|
||||
<svg
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400 dark:text-gray-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500">
|
||||
<MagnifyingGlass size={16} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -417,19 +363,7 @@
|
|||
? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700'}"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill={$showFavoritesOnly ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Heart size={16} weight={$showFavoritesOnly ? 'fill' : 'regular'} />
|
||||
<span>Favoriten</span>
|
||||
</button>
|
||||
|
||||
|
|
@ -466,7 +400,7 @@
|
|||
>
|
||||
<div
|
||||
class="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-sm font-semibold text-white"
|
||||
style="background-color: {$currentTheme.primary.default};"
|
||||
style="background-color: hsl(var(--color-primary));"
|
||||
>
|
||||
{authStore.user?.email?.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
|
|
@ -476,19 +410,9 @@
|
|||
</p>
|
||||
<p class="truncate text-xs text-gray-500 dark:text-gray-400">Account</p>
|
||||
</div>
|
||||
<svg
|
||||
class="h-4 w-4 text-gray-400 transition-transform {showUserMenu ? 'rotate-180' : ''}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-gray-400 transition-transform {showUserMenu ? 'rotate-180' : ''}">
|
||||
<CaretDown size={16} />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{#if showUserMenu}
|
||||
|
|
@ -500,28 +424,14 @@
|
|||
onclick={() => (showUserMenu = false)}
|
||||
class="flex items-center gap-3 px-4 py-3 text-sm text-gray-700 transition-colors hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
<User size={16} />
|
||||
Profil & Einstellungen
|
||||
</a>
|
||||
<button
|
||||
onclick={handleLogout}
|
||||
class="flex w-full items-center gap-3 border-t border-gray-200 px-4 py-3 text-left text-sm text-red-600 transition-colors hover:bg-gray-100 dark:border-gray-700 dark:text-red-400 dark:hover:bg-gray-700"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
<SignOut size={16} />
|
||||
Abmelden
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -542,7 +452,7 @@
|
|||
<button
|
||||
onclick={() => (showUserMenu = !showUserMenu)}
|
||||
class="flex h-9 w-9 items-center justify-center rounded-full text-sm font-semibold text-white"
|
||||
style="background-color: {$currentTheme.primary.default};"
|
||||
style="background-color: hsl(var(--color-primary));"
|
||||
>
|
||||
{authStore.user?.email?.charAt(0).toUpperCase()}
|
||||
</button>
|
||||
|
|
@ -561,14 +471,25 @@
|
|||
? 'bg-blue-50 text-blue-600 dark:bg-blue-950 dark:text-blue-400'
|
||||
: 'text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800'}"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 {active ? 'text-blue-600 dark:text-blue-400' : 'text-gray-400'}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={item.icon} />
|
||||
</svg>
|
||||
<span class="{active ? 'text-blue-600 dark:text-blue-400' : 'text-gray-400'}">
|
||||
{#if item.iconName === 'gallery'}
|
||||
<Image size={20} />
|
||||
{:else if item.iconName === 'board'}
|
||||
<SquaresFour size={20} />
|
||||
{:else if item.iconName === 'explore'}
|
||||
<MagnifyingGlass size={20} />
|
||||
{:else if item.iconName === 'generate'}
|
||||
<Lightning size={20} />
|
||||
{:else if item.iconName === 'upload'}
|
||||
<CloudArrowUp size={20} />
|
||||
{:else if item.iconName === 'tags'}
|
||||
<Tag size={20} />
|
||||
{:else if item.iconName === 'archive'}
|
||||
<Archive size={20} />
|
||||
{:else if item.iconName === 'subscription'}
|
||||
<CurrencyCircleDollar size={20} />
|
||||
{/if}
|
||||
</span>
|
||||
{item.label}
|
||||
</a>
|
||||
{/each}
|
||||
|
|
@ -577,28 +498,16 @@
|
|||
onclick={() => (showUserMenu = false)}
|
||||
class="flex items-center gap-3 border-b border-gray-100 px-4 py-3 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-gray-400">
|
||||
<User size={20} />
|
||||
</span>
|
||||
Profil & Einstellungen
|
||||
</a>
|
||||
<button
|
||||
onclick={handleLogout}
|
||||
class="flex w-full items-center gap-3 px-4 py-3 text-left text-sm font-medium text-red-600 transition-colors hover:bg-gray-50 dark:text-red-400 dark:hover:bg-gray-800"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
|
||||
/>
|
||||
</svg>
|
||||
<SignOut size={20} />
|
||||
Abmelden
|
||||
</button>
|
||||
</nav>
|
||||
|
|
@ -619,9 +528,23 @@
|
|||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800'}"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={item.icon} />
|
||||
</svg>
|
||||
{#if item.iconName === 'gallery'}
|
||||
<Image size={24} />
|
||||
{:else if item.iconName === 'board'}
|
||||
<SquaresFour size={24} />
|
||||
{:else if item.iconName === 'explore'}
|
||||
<MagnifyingGlass size={24} />
|
||||
{:else if item.iconName === 'generate'}
|
||||
<Lightning size={24} />
|
||||
{:else if item.iconName === 'upload'}
|
||||
<CloudArrowUp size={24} />
|
||||
{:else if item.iconName === 'tags'}
|
||||
<Tag size={24} />
|
||||
{:else if item.iconName === 'archive'}
|
||||
<Archive size={24} />
|
||||
{:else if item.iconName === 'subscription'}
|
||||
<CurrencyCircleDollar size={24} />
|
||||
{/if}
|
||||
<span class="text-xs font-medium">{item.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
themeVariant,
|
||||
themeMode,
|
||||
setThemeVariant,
|
||||
setThemeMode,
|
||||
currentTheme,
|
||||
} from '$lib/stores/theme';
|
||||
import { themes, type ThemeVariant } from '@picture/design-tokens';
|
||||
import type { ThemeMode } from '$lib/stores/theme';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import type { ThemeVariant, ThemeMode } from '@manacore/shared-theme';
|
||||
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
|
||||
import { CheckCircle, Info } from '@manacore/shared-icons';
|
||||
|
||||
interface ThemeOption {
|
||||
value: ThemeVariant;
|
||||
|
|
@ -22,8 +17,9 @@
|
|||
}
|
||||
|
||||
const themeOptions: ThemeOption[] = [
|
||||
{ value: 'default', label: 'Indigo', icon: '🔵' },
|
||||
{ value: 'sunset', label: 'Sunset', icon: '🌅' },
|
||||
{ value: 'lume', label: 'Lume', icon: '✨' },
|
||||
{ value: 'nature', label: 'Nature', icon: '🌿' },
|
||||
{ value: 'stone', label: 'Stone', icon: '🪨' },
|
||||
{ value: 'ocean', label: 'Ocean', icon: '🌊' },
|
||||
];
|
||||
|
||||
|
|
@ -32,18 +28,26 @@
|
|||
{ value: 'light', label: 'Hell', icon: '☀️' },
|
||||
{ value: 'dark', label: 'Dunkel', icon: '🌙' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the primary color for a variant based on current effective mode
|
||||
*/
|
||||
function getVariantColor(variant: ThemeVariant): string {
|
||||
const definition = THEME_DEFINITIONS[variant];
|
||||
const colors = theme.effectiveMode === 'dark' ? definition.dark : definition.light;
|
||||
return `hsl(${colors.primary})`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-2xl bg-white p-6 shadow-sm dark:bg-gray-900">
|
||||
<!-- Theme Variant Selection -->
|
||||
<div class="mb-8">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-100">Theme</h3>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
||||
{#each themeOptions as option}
|
||||
{@const isSelected = $themeVariant === option.value}
|
||||
{@const themePreview = themes[option.value]}
|
||||
{@const isSelected = theme.variant === option.value}
|
||||
<button
|
||||
onclick={() => setThemeVariant(option.value)}
|
||||
onclick={() => theme.setVariant(option.value)}
|
||||
class="relative rounded-xl border-2 p-4 transition-all hover:scale-105 {isSelected
|
||||
? 'border-blue-500 bg-blue-50 dark:border-blue-400 dark:bg-blue-950'
|
||||
: 'border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800'}"
|
||||
|
|
@ -61,31 +65,17 @@
|
|||
</div>
|
||||
|
||||
<!-- Color Preview -->
|
||||
<div class="flex justify-center gap-1">
|
||||
<div class="flex justify-center">
|
||||
<div
|
||||
class="h-5 w-5 rounded-full"
|
||||
style="background-color: {themePreview.colors.dark.primary.default}"
|
||||
></div>
|
||||
<div
|
||||
class="h-5 w-5 rounded-full"
|
||||
style="background-color: {themePreview.colors.dark.secondary.default}"
|
||||
style="background-color: {getVariantColor(option.value)}"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Checkmark -->
|
||||
{#if isSelected}
|
||||
<div class="absolute right-2 top-2">
|
||||
<svg
|
||||
class="h-6 w-6 text-blue-600 dark:text-blue-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircle size={24} weight="fill" class="text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -98,9 +88,9 @@
|
|||
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-gray-100">Modus</h3>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
{#each modeOptions as option}
|
||||
{@const isSelected = $themeMode === option.value}
|
||||
{@const isSelected = theme.mode === option.value}
|
||||
<button
|
||||
onclick={() => setThemeMode(option.value)}
|
||||
onclick={() => theme.setMode(option.value)}
|
||||
class="flex flex-col items-center justify-center gap-2 rounded-xl border px-4 py-3 transition-all {isSelected
|
||||
? 'border-blue-500 bg-blue-500 text-white'
|
||||
: 'border-gray-300 bg-white text-gray-700 hover:border-gray-400 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200'}"
|
||||
|
|
@ -112,23 +102,11 @@
|
|||
</div>
|
||||
|
||||
<!-- System Mode Info -->
|
||||
{#if $themeMode === 'system'}
|
||||
{#if theme.mode === 'system'}
|
||||
<div
|
||||
class="mt-4 flex items-start gap-2 rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-900 dark:bg-blue-950"
|
||||
>
|
||||
<svg
|
||||
class="mt-0.5 h-5 w-5 flex-shrink-0 text-blue-500 dark:text-blue-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Info size={20} class="mt-0.5 flex-shrink-0 text-blue-500 dark:text-blue-400" />
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300">
|
||||
Das Theme folgt den Systemeinstellungen deines Geräts
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { tags, selectedTags } from '$lib/stores/tags';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import { Check } from '@manacore/shared-icons';
|
||||
|
||||
type Tag = Database['public']['Tables']['tags']['Row'];
|
||||
|
||||
|
|
@ -38,14 +39,7 @@
|
|||
{/if}
|
||||
<span>{tag.name}</span>
|
||||
{#if selected}
|
||||
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<Check size={14} weight="bold" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { CircleNotch } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
||||
|
|
@ -45,20 +46,7 @@
|
|||
|
||||
<button {type} class={buttonClass} disabled={disabled || loading} {onclick}>
|
||||
{#if loading}
|
||||
<svg
|
||||
class="mr-2 h-4 w-4 animate-spin"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
<CircleNotch size={16} weight="bold" class="mr-2 animate-spin" />
|
||||
{/if}
|
||||
{@render children()}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -14,11 +14,22 @@
|
|||
import { archivedImages } from '$lib/stores/archive';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import {
|
||||
DownloadSimple,
|
||||
Link,
|
||||
Heart,
|
||||
Tag as TagIcon,
|
||||
Archive,
|
||||
ArrowCounterClockwise,
|
||||
Trash,
|
||||
CaretRight,
|
||||
Check,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
type Tag = Database['public']['Tables']['tags']['Row'];
|
||||
type TagType = Database['public']['Tables']['tags']['Row'];
|
||||
|
||||
let tagSubmenuElement = $state<HTMLElement | null>(null);
|
||||
let imageTags = $state<Tag[]>([]);
|
||||
let imageTags = $state<TagType[]>([]);
|
||||
|
||||
// Check if current image is archived
|
||||
const isArchived = $derived(
|
||||
|
|
@ -31,9 +42,11 @@
|
|||
// Check if current image belongs to current user
|
||||
const isOwnImage = $derived($contextMenu.image?.user_id === authStore.user?.id);
|
||||
|
||||
type IconName = 'download' | 'link' | 'heart' | 'tag' | 'archive' | 'restore' | 'trash';
|
||||
|
||||
interface MenuItem {
|
||||
label: string;
|
||||
icon: string;
|
||||
iconName: IconName;
|
||||
action: () => void;
|
||||
submenu?: boolean;
|
||||
divider?: boolean;
|
||||
|
|
@ -59,7 +72,7 @@
|
|||
showTagSubmenu(rect.right, rect.top);
|
||||
}
|
||||
|
||||
async function handleAddTag(tag: Tag) {
|
||||
async function handleAddTag(tag: TagType) {
|
||||
if (!$contextMenu.image) return;
|
||||
|
||||
try {
|
||||
|
|
@ -72,7 +85,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function handleRemoveTag(tag: Tag) {
|
||||
async function handleRemoveTag(tag: TagType) {
|
||||
if (!$contextMenu.image) return;
|
||||
|
||||
try {
|
||||
|
|
@ -181,23 +194,23 @@
|
|||
const menuItems = $derived([
|
||||
{
|
||||
label: 'Herunterladen',
|
||||
icon: 'M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4',
|
||||
iconName: 'download' as IconName,
|
||||
action: handleDownload,
|
||||
},
|
||||
{
|
||||
label: 'Link kopieren',
|
||||
icon: 'M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3',
|
||||
iconName: 'link' as IconName,
|
||||
action: handleCopyLink,
|
||||
},
|
||||
{
|
||||
label: isFavorite ? 'Aus Favoriten entfernen' : 'Zu Favoriten',
|
||||
icon: 'M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z',
|
||||
iconName: 'heart' as IconName,
|
||||
action: handleToggleFavorite,
|
||||
filled: isFavorite,
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
icon: 'M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z',
|
||||
iconName: 'tag' as IconName,
|
||||
action: () => {},
|
||||
submenu: true,
|
||||
divider: true,
|
||||
|
|
@ -207,9 +220,7 @@
|
|||
? [
|
||||
{
|
||||
label: isArchived ? 'Wiederherstellen' : 'Archivieren',
|
||||
icon: isArchived
|
||||
? 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15'
|
||||
: 'M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4',
|
||||
iconName: (isArchived ? 'restore' : 'archive') as IconName,
|
||||
action: handleArchive,
|
||||
divider: true,
|
||||
},
|
||||
|
|
@ -220,7 +231,7 @@
|
|||
? [
|
||||
{
|
||||
label: 'Löschen',
|
||||
icon: 'M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16',
|
||||
iconName: 'trash' as IconName,
|
||||
action: handleDelete,
|
||||
},
|
||||
]
|
||||
|
|
@ -278,24 +289,26 @@
|
|||
: ''}"
|
||||
role="menuitem"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4 flex-shrink-0"
|
||||
fill={item.filled ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={item.icon} />
|
||||
</svg>
|
||||
<span class="flex-shrink-0">
|
||||
{#if item.iconName === 'download'}
|
||||
<DownloadSimple size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{:else if item.iconName === 'link'}
|
||||
<Link size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{:else if item.iconName === 'heart'}
|
||||
<Heart size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{:else if item.iconName === 'tag'}
|
||||
<TagIcon size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{:else if item.iconName === 'archive'}
|
||||
<Archive size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{:else if item.iconName === 'restore'}
|
||||
<ArrowCounterClockwise size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{:else if item.iconName === 'trash'}
|
||||
<Trash size={16} weight={item.filled ? 'fill' : 'regular'} />
|
||||
{/if}
|
||||
</span>
|
||||
<span class="flex-1">{item.label}</span>
|
||||
{#if item.submenu}
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
<CaretRight size={16} />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
|
@ -339,19 +352,7 @@
|
|||
></div>
|
||||
<span class="flex-1 text-gray-700 dark:text-gray-300">{tag.name}</span>
|
||||
{#if hasTag}
|
||||
<svg
|
||||
class="h-4 w-4 text-blue-600 dark:text-blue-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="3"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<Check size={16} weight="bold" class="text-blue-600 dark:text-blue-400" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { showKeyboardShortcuts } from '$lib/stores/ui';
|
||||
import { X } from '@manacore/shared-icons';
|
||||
|
||||
interface Shortcut {
|
||||
key: string;
|
||||
|
|
@ -56,14 +57,7 @@
|
|||
class="flex h-10 w-10 items-center justify-center rounded-full bg-gray-100/80 text-gray-600 backdrop-blur-xl transition-all hover:bg-gray-200/80 dark:bg-gray-800/80 dark:text-gray-400 dark:hover:bg-gray-700/80"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { X } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -47,6 +48,12 @@
|
|||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
|
|
@ -54,7 +61,7 @@
|
|||
bind:this={modalElement}
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 dark:bg-black/70"
|
||||
onclick={handleBackdropClick}
|
||||
onkeydown={(e) => e.key === 'Enter' && handleBackdropClick(e)}
|
||||
onkeydown={handleKeydown}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="-1"
|
||||
|
|
@ -69,14 +76,7 @@
|
|||
class="absolute right-4 top-4 z-10 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-black/50 dark:bg-gray-800 dark:hover:bg-gray-700"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={24} weight="bold" />
|
||||
</button>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,32 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { toasts, dismissToast, type Toast } from '$lib/stores/toast';
|
||||
import { fly, fade } from 'svelte/transition';
|
||||
|
||||
function getToastIcon(type: Toast['type']) {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return {
|
||||
path: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
color: 'text-green-500',
|
||||
};
|
||||
case 'error':
|
||||
return {
|
||||
path: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
color: 'text-red-500',
|
||||
};
|
||||
case 'warning':
|
||||
return {
|
||||
path: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
|
||||
color: 'text-yellow-500',
|
||||
};
|
||||
case 'info':
|
||||
default:
|
||||
return {
|
||||
path: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
color: 'text-blue-500',
|
||||
};
|
||||
}
|
||||
}
|
||||
import { CheckCircle, XCircle, Warning, Info, X } from '@manacore/shared-icons';
|
||||
|
||||
function getToastBgColor(type: Toast['type']) {
|
||||
switch (type) {
|
||||
|
|
@ -45,21 +20,23 @@
|
|||
|
||||
<div class="pointer-events-none fixed bottom-4 right-4 z-[100] flex flex-col gap-2">
|
||||
{#each $toasts as toast (toast.id)}
|
||||
{@const icon = getToastIcon(toast.type)}
|
||||
{@const bgColor = getToastBgColor(toast.type)}
|
||||
<div
|
||||
transition:fly={{ y: 50, duration: 300 }}
|
||||
class="pointer-events-auto flex min-w-[320px] items-start gap-3 rounded-lg border p-4 shadow-lg {bgColor}"
|
||||
role="alert"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 flex-shrink-0 {icon.color}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={icon.path} />
|
||||
</svg>
|
||||
<span class="flex-shrink-0">
|
||||
{#if toast.type === 'success'}
|
||||
<CheckCircle size={24} weight="regular" class="text-green-500" />
|
||||
{:else if toast.type === 'error'}
|
||||
<XCircle size={24} weight="regular" class="text-red-500" />
|
||||
{:else if toast.type === 'warning'}
|
||||
<Warning size={24} weight="regular" class="text-yellow-500" />
|
||||
{:else}
|
||||
<Info size={24} weight="regular" class="text-blue-500" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<p class="flex-1 text-sm font-medium text-gray-900">{toast.message}</p>
|
||||
|
||||
|
|
@ -68,14 +45,7 @@
|
|||
class="flex-shrink-0 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2"
|
||||
aria-label="Schließen"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={20} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { viewMode, cycleViewMode, type ViewMode } from '$lib/stores/view';
|
||||
|
||||
function getIcon(mode: ViewMode) {
|
||||
switch (mode) {
|
||||
case 'single':
|
||||
return 'M4 6h16M4 12h16M4 18h16'; // List icon
|
||||
case 'grid3':
|
||||
return 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z'; // 2x2 grid
|
||||
case 'grid5':
|
||||
return 'M4 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM10 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM16 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM4 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1v-2zM10 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2zM16 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2z'; // 3x2 grid
|
||||
}
|
||||
}
|
||||
import { List, SquaresFour, Squares } from '@manacore/shared-icons';
|
||||
|
||||
function getLabel(mode: ViewMode) {
|
||||
switch (mode) {
|
||||
|
|
@ -29,8 +19,12 @@
|
|||
class="flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
||||
title="Ansicht wechseln ({getLabel($viewMode)})"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d={getIcon($viewMode)} />
|
||||
</svg>
|
||||
{#if $viewMode === 'single'}
|
||||
<List size={20} />
|
||||
{:else if $viewMode === 'grid3'}
|
||||
<SquaresFour size={20} />
|
||||
{:else}
|
||||
<Squares size={20} />
|
||||
{/if}
|
||||
<span class="hidden sm:inline">{getLabel($viewMode)}</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { validateImage } from '$lib/api/upload';
|
||||
import type { UploadProgress } from '$lib/api/upload';
|
||||
import { CloudArrowUp, X } from '@manacore/shared-icons';
|
||||
|
||||
interface Props {
|
||||
onFilesSelected: (files: File[]) => void;
|
||||
|
|
@ -104,19 +105,11 @@
|
|||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mb-4 h-16 w-16 {isDragging ? 'text-blue-500' : 'text-gray-400 dark:text-gray-600'}"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
<CloudArrowUp
|
||||
size={64}
|
||||
weight="regular"
|
||||
class="mb-4 {isDragging ? 'text-blue-500' : 'text-gray-400 dark:text-gray-600'}"
|
||||
/>
|
||||
|
||||
<h3 class="mb-2 text-xl font-semibold text-gray-900 dark:text-white">
|
||||
{isDragging ? 'Loslassen zum Hochladen' : 'Bilder hochladen'}
|
||||
|
|
@ -178,14 +171,7 @@
|
|||
onclick={() => removeFile(index)}
|
||||
class="absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full bg-white/90 text-gray-900 opacity-0 transition-opacity hover:bg-white group-hover:opacity-100"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={16} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
|
|
@ -255,14 +241,7 @@
|
|||
onclick={handleUpload}
|
||||
class="flex items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 text-base font-medium text-white transition-all hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
<CloudArrowUp size={20} weight="bold" />
|
||||
{selectedFiles.length}
|
||||
{selectedFiles.length === 1 ? 'Bild' : 'Bilder'} hochladen
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
import type { Image } from '$lib/api/images';
|
||||
|
||||
export const archivedImages = writable<Image[]>([]);
|
||||
export const isLoadingArchive = writable(false);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { writable, derived } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { BoardWithCount } from '$lib/api/boards';
|
||||
|
||||
type Board = Database['public']['Tables']['boards']['Row'];
|
||||
import type { Board, BoardWithCount } from '$lib/api/boards';
|
||||
|
||||
// Current boards list
|
||||
export const boards = writable<BoardWithCount[]>([]);
|
||||
|
|
@ -30,9 +27,9 @@ export const shareBoardId = writable<string | null>(null);
|
|||
|
||||
// Board settings (for canvas)
|
||||
export const boardSettings = derived(currentBoard, ($currentBoard) => ({
|
||||
width: $currentBoard?.canvas_width || 2000,
|
||||
height: $currentBoard?.canvas_height || 1500,
|
||||
backgroundColor: $currentBoard?.background_color || '#ffffff',
|
||||
width: $currentBoard?.canvasWidth || 2000,
|
||||
height: $currentBoard?.canvasHeight || 1500,
|
||||
backgroundColor: $currentBoard?.backgroundColor || '#ffffff',
|
||||
}));
|
||||
|
||||
// Helper functions for board management
|
||||
|
|
@ -59,7 +56,7 @@ export function removeBoardFromList(boardId: string) {
|
|||
export function incrementBoardItemCount(boardId: string) {
|
||||
boards.update((current) =>
|
||||
current.map((board) =>
|
||||
board.id === boardId ? { ...board, item_count: board.item_count + 1 } : board
|
||||
board.id === boardId ? { ...board, itemCount: board.itemCount + 1 } : board
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -67,7 +64,7 @@ export function incrementBoardItemCount(boardId: string) {
|
|||
export function decrementBoardItemCount(boardId: string) {
|
||||
boards.update((current) =>
|
||||
current.map((board) =>
|
||||
board.id === boardId ? { ...board, item_count: Math.max(0, board.item_count - 1) } : board
|
||||
board.id === boardId ? { ...board, itemCount: Math.max(0, board.itemCount - 1) } : board
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,9 @@ export const canvasStore = {
|
|||
},
|
||||
|
||||
updateItem(id: string, updates: Partial<BoardItem>) {
|
||||
canvasItems = canvasItems.map((item) => (item.id === id ? { ...item, ...updates } : item));
|
||||
canvasItems = canvasItems.map((item) =>
|
||||
item.id === id ? ({ ...item, ...updates } as BoardItem) : item
|
||||
);
|
||||
saveToHistory();
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export function addCanvasItem(item: BoardItem) {
|
|||
|
||||
export function updateCanvasItem(id: string, updates: Partial<BoardItem>) {
|
||||
canvasItems.update((items) =>
|
||||
items.map((item) => (item.id === id ? { ...item, ...updates } : item))
|
||||
items.map((item) => (item.id === id ? ({ ...item, ...updates } as BoardItem) : item))
|
||||
);
|
||||
saveToHistory();
|
||||
}
|
||||
|
|
@ -137,14 +137,14 @@ export function deselectAll() {
|
|||
|
||||
export function bringToFront(id: string) {
|
||||
const items = get(canvasItems);
|
||||
const maxZIndex = Math.max(...items.map((item) => item.z_index));
|
||||
updateCanvasItem(id, { z_index: maxZIndex + 1 });
|
||||
const maxZIndex = Math.max(...items.map((item) => item.zIndex));
|
||||
updateCanvasItem(id, { zIndex: maxZIndex + 1 });
|
||||
}
|
||||
|
||||
export function sendToBack(id: string) {
|
||||
const items = get(canvasItems);
|
||||
const minZIndex = Math.min(...items.map((item) => item.z_index));
|
||||
updateCanvasItem(id, { z_index: minZIndex - 1 });
|
||||
const minZIndex = Math.min(...items.map((item) => item.zIndex));
|
||||
updateCanvasItem(id, { zIndex: minZIndex - 1 });
|
||||
}
|
||||
|
||||
export function moveForward(id: string) {
|
||||
|
|
@ -152,11 +152,11 @@ export function moveForward(id: string) {
|
|||
const item = items.find((i) => i.id === id);
|
||||
if (!item) return;
|
||||
|
||||
const itemsAbove = items.filter((i) => i.z_index > item.z_index);
|
||||
const itemsAbove = items.filter((i) => i.zIndex > item.zIndex);
|
||||
if (itemsAbove.length === 0) return;
|
||||
|
||||
const nextZIndex = Math.min(...itemsAbove.map((i) => i.z_index));
|
||||
updateCanvasItem(id, { z_index: nextZIndex + 0.5 });
|
||||
const nextZIndex = Math.min(...itemsAbove.map((i) => i.zIndex));
|
||||
updateCanvasItem(id, { zIndex: nextZIndex + 0.5 });
|
||||
}
|
||||
|
||||
export function moveBackward(id: string) {
|
||||
|
|
@ -164,11 +164,11 @@ export function moveBackward(id: string) {
|
|||
const item = items.find((i) => i.id === id);
|
||||
if (!item) return;
|
||||
|
||||
const itemsBelow = items.filter((i) => i.z_index < item.z_index);
|
||||
const itemsBelow = items.filter((i) => i.zIndex < item.zIndex);
|
||||
if (itemsBelow.length === 0) return;
|
||||
|
||||
const prevZIndex = Math.max(...itemsBelow.map((i) => i.z_index));
|
||||
updateCanvasItem(id, { z_index: prevZIndex - 0.5 });
|
||||
const prevZIndex = Math.max(...itemsBelow.map((i) => i.zIndex));
|
||||
updateCanvasItem(id, { zIndex: prevZIndex - 0.5 });
|
||||
}
|
||||
|
||||
// Zoom functions
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export const contextMenuStore = {
|
|||
contextMenuState = { ...initialState };
|
||||
},
|
||||
|
||||
showTagSubmenu(x: number, y: number) {
|
||||
openTagSubmenu(x: number, y: number) {
|
||||
contextMenuState = {
|
||||
...contextMenuState,
|
||||
showTagSubmenu: true,
|
||||
|
|
@ -95,7 +95,7 @@ export function hideContextMenu() {
|
|||
}
|
||||
|
||||
export function showTagSubmenu(x: number, y: number) {
|
||||
contextMenuStore.showTagSubmenu(x, y);
|
||||
contextMenuStore.openTagSubmenu(x, y);
|
||||
}
|
||||
|
||||
export function hideTagSubmenu() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
import type { Image } from '$lib/api/images';
|
||||
|
||||
interface ContextMenuState {
|
||||
visible: boolean;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
import type { Image } from '$lib/api/images';
|
||||
|
||||
export const exploreImages = writable<Image[]>([]);
|
||||
export const isLoadingExplore = writable(false);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Image = Database['public']['Tables']['images']['Row'];
|
||||
import type { Image } from '$lib/api/images';
|
||||
|
||||
export const images = writable<Image[]>([]);
|
||||
export const selectedImage = writable<Image | null>(null);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Model = Database['public']['Tables']['models']['Row'];
|
||||
import type { Model } from '$lib/api/models';
|
||||
|
||||
export const models = writable<Model[]>([]);
|
||||
export const selectedModel = writable<Model | null>(null);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Tag = Database['public']['Tables']['tags']['Row'];
|
||||
import type { Tag } from '$lib/api/tags';
|
||||
|
||||
export const tags = writable<Tag[]>([]);
|
||||
export const selectedTags = writable<string[]>([]);
|
||||
|
|
|
|||
|
|
@ -1,122 +1,23 @@
|
|||
import { writable, derived } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import { themes, type ThemeVariant } from '@picture/design-tokens';
|
||||
import { createThemeStore, APP_THEME_CONFIGS } from '@manacore/shared-theme';
|
||||
|
||||
export type ThemeMode = 'light' | 'dark' | 'system';
|
||||
/**
|
||||
* Picture theme store using the global shared theme system
|
||||
*
|
||||
* Usage:
|
||||
* - theme.mode: 'light' | 'dark' | 'system'
|
||||
* - theme.variant: 'lume' | 'nature' | 'stone' | 'ocean'
|
||||
* - theme.effectiveMode: 'light' | 'dark' (resolved from system if needed)
|
||||
* - theme.isDark: boolean
|
||||
* - theme.setMode(mode): Set theme mode
|
||||
* - theme.setVariant(variant): Set theme variant
|
||||
* - theme.toggleMode(): Toggle between light/dark
|
||||
* - theme.cycleMode(): Cycle through light → dark → system
|
||||
* - theme.initialize(): Initialize and apply theme (call in onMount)
|
||||
*
|
||||
* CSS Variables applied automatically:
|
||||
* --color-primary, --color-background, --color-foreground, etc.
|
||||
*/
|
||||
export const theme = createThemeStore(APP_THEME_CONFIGS.picture);
|
||||
|
||||
interface ThemeState {
|
||||
variant: ThemeVariant;
|
||||
mode: ThemeMode;
|
||||
}
|
||||
|
||||
const THEME_VARIANT_KEY = 'picture_theme_variant';
|
||||
const THEME_MODE_KEY = 'picture_theme_mode';
|
||||
|
||||
// Load initial values from localStorage
|
||||
function loadInitialTheme(): ThemeState {
|
||||
if (!browser) {
|
||||
return { variant: 'default', mode: 'system' };
|
||||
}
|
||||
|
||||
const savedVariant = localStorage.getItem(THEME_VARIANT_KEY) as ThemeVariant | null;
|
||||
const savedMode = localStorage.getItem(THEME_MODE_KEY) as ThemeMode | null;
|
||||
|
||||
return {
|
||||
variant: savedVariant || 'default',
|
||||
mode: savedMode || 'system',
|
||||
};
|
||||
}
|
||||
|
||||
// Create stores with initial values
|
||||
const initialTheme = loadInitialTheme();
|
||||
export const themeVariant = writable<ThemeVariant>(initialTheme.variant);
|
||||
export const themeMode = writable<ThemeMode>(initialTheme.mode);
|
||||
|
||||
// Derive the actual mode (resolve 'system' to 'light' or 'dark')
|
||||
export const actualMode = derived(themeMode, ($mode) => {
|
||||
if ($mode === 'system' && browser) {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
return $mode === 'system' ? 'dark' : $mode;
|
||||
});
|
||||
|
||||
// Derive the current theme object
|
||||
export const currentTheme = derived([themeVariant, actualMode], ([$variant, $actualMode]) => {
|
||||
const theme = themes[$variant];
|
||||
return theme.colors[$actualMode];
|
||||
});
|
||||
|
||||
// Actions
|
||||
export function setThemeVariant(variant: ThemeVariant) {
|
||||
themeVariant.set(variant);
|
||||
if (browser) {
|
||||
localStorage.setItem(THEME_VARIANT_KEY, variant);
|
||||
}
|
||||
}
|
||||
|
||||
export function setThemeMode(mode: ThemeMode) {
|
||||
themeMode.set(mode);
|
||||
if (browser) {
|
||||
localStorage.setItem(THEME_MODE_KEY, mode);
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleThemeMode() {
|
||||
themeMode.update((current) => {
|
||||
const newMode = current === 'dark' ? 'light' : 'dark';
|
||||
if (browser) {
|
||||
localStorage.setItem(THEME_MODE_KEY, newMode);
|
||||
}
|
||||
return newMode;
|
||||
});
|
||||
}
|
||||
|
||||
// Listen to system theme changes and apply theme to DOM
|
||||
if (browser) {
|
||||
// Listen to system color scheme changes
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
mediaQuery.addEventListener('change', () => {
|
||||
// Force re-evaluation of actualMode when system preference changes
|
||||
themeMode.update((mode) => mode);
|
||||
});
|
||||
|
||||
// Apply CSS custom properties and background colors
|
||||
currentTheme.subscribe((theme) => {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Primary colors
|
||||
root.style.setProperty('--color-primary', theme.primary.default);
|
||||
root.style.setProperty('--color-primary-hover', theme.primary.hover);
|
||||
root.style.setProperty('--color-primary-active', theme.primary.active);
|
||||
|
||||
// Background colors
|
||||
root.style.setProperty('--color-background', theme.background);
|
||||
root.style.setProperty('--color-surface', theme.surface);
|
||||
root.style.setProperty('--color-elevated', theme.elevated);
|
||||
|
||||
// Text colors
|
||||
root.style.setProperty('--color-text-primary', theme.text.primary);
|
||||
root.style.setProperty('--color-text-secondary', theme.text.secondary);
|
||||
root.style.setProperty('--color-text-tertiary', theme.text.tertiary);
|
||||
|
||||
// Border colors
|
||||
root.style.setProperty('--color-border', theme.border);
|
||||
root.style.setProperty('--color-divider', theme.divider);
|
||||
|
||||
// Status colors
|
||||
root.style.setProperty('--color-success', theme.success);
|
||||
root.style.setProperty('--color-error', theme.error);
|
||||
root.style.setProperty('--color-warning', theme.warning);
|
||||
root.style.setProperty('--color-info', theme.info);
|
||||
|
||||
// Apply background color to body
|
||||
document.body.style.backgroundColor = theme.background;
|
||||
document.body.style.color = theme.text.primary;
|
||||
});
|
||||
|
||||
// Apply dark/light mode class to document element
|
||||
actualMode.subscribe((mode) => {
|
||||
document.documentElement.classList.remove('light', 'dark');
|
||||
document.documentElement.classList.add(mode);
|
||||
});
|
||||
}
|
||||
// Export theme types for convenience
|
||||
export type { ThemeMode, ThemeVariant } from '@manacore/shared-theme';
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { initPostHog, analytics } from '$lib/analytics/posthog';
|
||||
|
||||
// Import theme stores to initialize them
|
||||
import '$lib/stores/theme';
|
||||
// Import and initialize theme
|
||||
import { theme } from '$lib/stores/theme';
|
||||
|
||||
// Initialize i18n
|
||||
import '$lib/i18n';
|
||||
|
|
@ -15,6 +15,9 @@
|
|||
let { children, data } = $props();
|
||||
|
||||
onMount(async () => {
|
||||
// Initialize theme (applies CSS variables and loads from localStorage)
|
||||
const cleanupTheme = theme.initialize();
|
||||
|
||||
// Initialize PostHog
|
||||
initPostHog();
|
||||
|
||||
|
|
@ -27,6 +30,10 @@
|
|||
email: authStore.user.email,
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
cleanupTheme();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import { PillNavigation } from '@manacore/shared-ui';
|
||||
import type { PillNavItem, PillNavElement } from '@manacore/shared-ui';
|
||||
import KeyboardShortcutsModal from '$lib/components/ui/KeyboardShortcutsModal.svelte';
|
||||
import { currentTheme, actualMode, toggleThemeMode } from '$lib/stores/theme';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { isUIVisible, toggleUI, showKeyboardShortcuts } from '$lib/stores/ui';
|
||||
import { viewMode, setViewMode, type ViewMode } from '$lib/stores/view';
|
||||
import { browser } from '$app/environment';
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
|
||||
function handleToggleTheme() {
|
||||
toggleThemeMode();
|
||||
theme.toggleMode();
|
||||
}
|
||||
|
||||
// Client-side auth check
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else if authStore.user}
|
||||
<div class="min-h-screen" style="background-color: {$currentTheme.background};">
|
||||
<div class="min-h-screen" style="background-color: hsl(var(--color-background));">
|
||||
<!-- PillNavigation (conditionally visible) -->
|
||||
{#if $isUIVisible}
|
||||
<PillNavigation
|
||||
|
|
@ -168,7 +168,7 @@
|
|||
homeRoute="/app/gallery"
|
||||
onLogout={handleLogout}
|
||||
onToggleTheme={handleToggleTheme}
|
||||
isDark={$actualMode === 'dark'}
|
||||
isDark={theme.isDark}
|
||||
{isSidebarMode}
|
||||
onModeChange={handleModeChange}
|
||||
{isCollapsed}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
import ArchivedImageModal from '$lib/components/archive/ArchivedImageModal.svelte';
|
||||
import ImageSkeleton from '$lib/components/ui/ImageSkeleton.svelte';
|
||||
import ContextMenu from '$lib/components/ui/ContextMenu.svelte';
|
||||
import { Archive } from '@manacore/shared-icons';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
|
|
@ -100,19 +101,7 @@
|
|||
{:else if $archivedImages.length === 0}
|
||||
<div class="flex min-h-[400px] items-center justify-center px-4 py-8">
|
||||
<div class="text-center">
|
||||
<svg
|
||||
class="mx-auto h-16 w-16 text-gray-400 dark:text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"
|
||||
/>
|
||||
</svg>
|
||||
<Archive size={64} weight="thin" class="mx-auto text-gray-400 dark:text-gray-600" />
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">Kein Archiv</h3>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">
|
||||
Archiviere Bilder aus deiner Galerie, um sie organisiert zu halten ohne sie zu löschen
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import Modal from '$lib/components/ui/Modal.svelte';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import { Plus, SquaresFour, Image, Trash } from '@manacore/shared-icons';
|
||||
|
||||
let loadingMore = $state(false);
|
||||
let observer: IntersectionObserver | null = null;
|
||||
|
|
@ -172,9 +173,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<Button onclick={() => showCreateBoardModal.set(true)}>
|
||||
<svg class="mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
<Plus size={20} class="mr-2" />
|
||||
Neues Board
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -193,19 +192,7 @@
|
|||
{:else if $boards.length === 0}
|
||||
<!-- Empty State -->
|
||||
<div class="flex flex-col items-center justify-center py-20">
|
||||
<svg
|
||||
class="h-24 w-24 text-gray-300 dark:text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M4 5a1 1 0 011-1h4a1 1 0 011 1v7a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v7a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 16a1 1 0 011-1h4a1 1 0 011 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-3zM14 16a1 1 0 011-1h4a1 1 0 011 1v3a1 1 0 01-1 1h-4a1 1 0 01-1-1v-3z"
|
||||
/>
|
||||
</svg>
|
||||
<SquaresFour size={96} weight="thin" class="text-gray-300 dark:text-gray-600" />
|
||||
<h3 class="mt-4 text-xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
Keine Boards vorhanden
|
||||
</h3>
|
||||
|
|
@ -237,19 +224,7 @@
|
|||
/>
|
||||
{:else}
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<svg
|
||||
class="h-16 w-16 text-gray-300 dark:text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<Image size={64} weight="thin" class="text-gray-300 dark:text-gray-600" />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -285,14 +260,7 @@
|
|||
Duplizieren
|
||||
</Button>
|
||||
<Button size="sm" variant="danger" onclick={() => confirmDelete(board.id)}>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<Trash size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import CanvasToolbar from '$lib/components/board/CanvasToolbar.svelte';
|
||||
import ImagePickerModal from '$lib/components/board/ImagePickerModal.svelte';
|
||||
import ImagePropertiesPanel from '$lib/components/board/ImagePropertiesPanel.svelte';
|
||||
import { X, SlidersHorizontal } from '@manacore/shared-icons';
|
||||
|
||||
const boardId = $derived($page.params.id);
|
||||
|
||||
|
|
@ -152,25 +153,9 @@
|
|||
title={$showPropertiesPanel ? 'Eigenschaften ausblenden' : 'Eigenschaften anzeigen'}
|
||||
>
|
||||
{#if $showPropertiesPanel}
|
||||
<!-- Close Icon -->
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<X size={24} />
|
||||
{:else}
|
||||
<!-- Settings/Sliders Icon -->
|
||||
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
|
||||
/>
|
||||
</svg>
|
||||
<SlidersHorizontal size={24} />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
import ImageSkeleton from '$lib/components/ui/ImageSkeleton.svelte';
|
||||
import ViewModeSwitcher from '$lib/components/ui/ViewModeSwitcher.svelte';
|
||||
import ContextMenu from '$lib/components/ui/ContextMenu.svelte';
|
||||
import { MagnifyingGlass, X, Heart } from '@manacore/shared-icons';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
import type { ViewMode } from '$lib/stores/view';
|
||||
|
|
@ -179,19 +180,7 @@
|
|||
placeholder="Bilder suchen..."
|
||||
class="w-full rounded-full border border-gray-300/50 bg-white/80 px-4 py-2 pl-10 text-sm text-gray-900 placeholder-gray-500 backdrop-blur-xl transition-all focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:border-gray-600/50 dark:bg-gray-800/80 dark:text-gray-100 dark:placeholder-gray-400"
|
||||
/>
|
||||
<svg
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400 dark:text-gray-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<MagnifyingGlass size={16} class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" />
|
||||
{#if searchInput}
|
||||
<button
|
||||
onclick={() => {
|
||||
|
|
@ -201,9 +190,7 @@
|
|||
}}
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<X size={16} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -226,19 +213,7 @@
|
|||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-100/80 text-gray-700 backdrop-blur-xl hover:bg-gray-200/80 dark:bg-gray-800/80 dark:text-gray-300 dark:hover:bg-gray-700/80'}"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill={$showExploreFavoritesOnly ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Heart size={16} weight={$showExploreFavoritesOnly ? 'fill' : 'regular'} />
|
||||
<span>Favoriten</span>
|
||||
</button>
|
||||
|
||||
|
|
@ -267,19 +242,7 @@
|
|||
{:else if $exploreImages.length === 0}
|
||||
<div class="flex min-h-[400px] items-center justify-center px-4">
|
||||
<div class="text-center">
|
||||
<svg
|
||||
class="mx-auto h-16 w-16 text-gray-400 dark:text-gray-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<MagnifyingGlass size={64} weight="thin" class="mx-auto text-gray-400 dark:text-gray-500" />
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
Keine Bilder gefunden
|
||||
</h3>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
import ImageSkeleton from '$lib/components/ui/ImageSkeleton.svelte';
|
||||
import ViewModeSwitcher from '$lib/components/ui/ViewModeSwitcher.svelte';
|
||||
import TagPills from '$lib/components/tags/TagPills.svelte';
|
||||
import { Heart } from '@manacore/shared-icons';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let loadingMore = $state(false);
|
||||
|
|
@ -130,19 +131,7 @@
|
|||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-100/80 text-gray-700 backdrop-blur-xl hover:bg-gray-200/80 dark:bg-gray-800/80 dark:text-gray-300 dark:hover:bg-gray-700/80'}"
|
||||
>
|
||||
<svg
|
||||
class="h-4 w-4"
|
||||
fill={$showFavoritesOnly ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
|
||||
/>
|
||||
</svg>
|
||||
<Heart size={16} weight={$showFavoritesOnly ? 'fill' : 'regular'} />
|
||||
<span>Favoriten</span>
|
||||
</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import GenerateForm from '$lib/components/generate/GenerateForm.svelte';
|
||||
import { CheckCircle } from '@manacore/shared-icons';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -24,74 +25,26 @@
|
|||
<h3 class="mb-3 text-lg font-semibold text-gray-900">Tips for better results:</h3>
|
||||
<ul class="space-y-2 text-sm text-gray-700">
|
||||
<li class="flex items-start">
|
||||
<svg
|
||||
class="mr-2 mt-0.5 h-5 w-5 flex-shrink-0 text-blue-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircle size={20} class="mr-2 mt-0.5 flex-shrink-0 text-blue-600" />
|
||||
<span
|
||||
><strong>Be specific:</strong> Include details about style, mood, colors, and composition</span
|
||||
>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg
|
||||
class="mr-2 mt-0.5 h-5 w-5 flex-shrink-0 text-blue-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircle size={20} class="mr-2 mt-0.5 flex-shrink-0 text-blue-600" />
|
||||
<span
|
||||
><strong>Use descriptive words:</strong> "Vibrant sunset over mountains" is better than "sunset"</span
|
||||
>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg
|
||||
class="mr-2 mt-0.5 h-5 w-5 flex-shrink-0 text-blue-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircle size={20} class="mr-2 mt-0.5 flex-shrink-0 text-blue-600" />
|
||||
<span
|
||||
><strong>Negative prompts:</strong> Use to exclude unwanted elements (e.g., "blurry, distorted,
|
||||
low quality")</span
|
||||
>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<svg
|
||||
class="mr-2 mt-0.5 h-5 w-5 flex-shrink-0 text-blue-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircle size={20} class="mr-2 mt-0.5 flex-shrink-0 text-blue-600" />
|
||||
<span
|
||||
><strong>Try different models:</strong> Each model has unique strengths and artistic styles</span
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { SubscriptionPage } from '@manacore/shared-subscription-ui';
|
||||
import { currentTheme } from '$lib/stores/theme';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
|
||||
function handleSubscribe(planId: string) {
|
||||
|
|
@ -18,7 +17,7 @@
|
|||
<title>Abonnement - Picture</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="min-h-screen p-4 md:p-8" style="background-color: {$currentTheme.background};">
|
||||
<div class="min-h-screen p-4 md:p-8" style="background-color: hsl(var(--color-background));">
|
||||
<div class="mx-auto max-w-6xl">
|
||||
<SubscriptionPage
|
||||
appName="Picture"
|
||||
|
|
|
|||
|
|
@ -3,13 +3,14 @@
|
|||
import { tags, isLoadingTags } from '$lib/stores/tags';
|
||||
import { getAllTags, createTag, updateTag, deleteTag } from '$lib/api/tags';
|
||||
import { showToast } from '$lib/stores/toast';
|
||||
import { Plus, Tag as TagIcon, PencilSimple, Trash } from '@manacore/shared-icons';
|
||||
import type { Database } from '@picture/shared/types';
|
||||
|
||||
type Tag = Database['public']['Tables']['tags']['Row'];
|
||||
type TagType = Database['public']['Tables']['tags']['Row'];
|
||||
|
||||
let showCreateModal = $state(false);
|
||||
let showEditModal = $state(false);
|
||||
let editingTag = $state<Tag | null>(null);
|
||||
let editingTag = $state<TagType | null>(null);
|
||||
let newTagName = $state('');
|
||||
let newTagColor = $state('#3B82F6');
|
||||
let editTagName = $state('');
|
||||
|
|
@ -62,7 +63,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
function openEditModal(tag: Tag) {
|
||||
function openEditModal(tag: TagType) {
|
||||
editingTag = tag;
|
||||
editTagName = tag.name;
|
||||
editTagColor = tag.color || '#3B82F6';
|
||||
|
|
@ -119,14 +120,7 @@
|
|||
onclick={() => (showCreateModal = true)}
|
||||
class="flex items-center gap-2 rounded-2xl bg-blue-600/90 px-6 py-3 text-sm font-medium text-white backdrop-blur-xl transition-all hover:bg-blue-700/90 dark:bg-blue-500/90 dark:hover:bg-blue-600/90"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
<Plus size={20} />
|
||||
Neuer Tag
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -142,19 +136,7 @@
|
|||
<div
|
||||
class="rounded-3xl border border-gray-200/50 bg-white/80 p-12 text-center backdrop-blur-xl dark:border-gray-700/50 dark:bg-gray-900/80"
|
||||
>
|
||||
<svg
|
||||
class="mx-auto h-16 w-16 text-gray-400 dark:text-gray-500"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
|
||||
/>
|
||||
</svg>
|
||||
<TagIcon size={64} weight="thin" class="mx-auto text-gray-400 dark:text-gray-500" />
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
Keine Tags vorhanden
|
||||
</h3>
|
||||
|
|
@ -187,28 +169,14 @@
|
|||
class="rounded-lg bg-gray-100/80 p-2 text-gray-600 backdrop-blur-xl transition-all hover:bg-gray-200/80 dark:bg-gray-800/80 dark:text-gray-400 dark:hover:bg-gray-700/80"
|
||||
aria-label="Bearbeiten"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
<PencilSimple size={16} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleDeleteTag(tag.id)}
|
||||
class="rounded-lg bg-red-100/80 p-2 text-red-600 backdrop-blur-xl transition-all hover:bg-red-200/80 dark:bg-red-900/20 dark:text-red-400 dark:hover:bg-red-900/30"
|
||||
aria-label="Löschen"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
<Trash size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
import { showToast } from '$lib/stores/toast';
|
||||
import DropZone from '$lib/components/upload/DropZone.svelte';
|
||||
import { images } from '$lib/stores/images';
|
||||
import { Check, Image, CloudArrowUp, CheckCircle } from '@manacore/shared-icons';
|
||||
|
||||
let uploading = $state(false);
|
||||
let uploadProgress = $state<UploadProgress[]>([]);
|
||||
|
|
@ -20,7 +21,7 @@
|
|||
successCount = 0;
|
||||
|
||||
try {
|
||||
const uploadedImages = await uploadMultipleImages(files, authStore.user.id, (progress) => {
|
||||
const uploadedImages = await uploadMultipleImages(files, (progress) => {
|
||||
uploadProgress = progress;
|
||||
});
|
||||
|
||||
|
|
@ -70,19 +71,7 @@
|
|||
<div
|
||||
class="mb-6 flex items-center gap-3 rounded-2xl border border-green-200 bg-green-50 p-4 dark:border-green-900 dark:bg-green-950/20"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 flex-shrink-0 text-green-600 dark:text-green-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
<Check size={24} class="flex-shrink-0 text-green-600 dark:text-green-400" />
|
||||
<div>
|
||||
<p class="font-medium text-green-900 dark:text-green-100">Upload erfolgreich!</p>
|
||||
<p class="text-sm text-green-700 dark:text-green-300">
|
||||
|
|
@ -105,19 +94,7 @@
|
|||
<div
|
||||
class="mb-2 flex h-10 w-10 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-950"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-blue-600 dark:text-blue-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<Image size={20} class="text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<h3 class="mb-1 font-semibold text-gray-900 dark:text-white">Unterstützte Formate</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
|
|
@ -131,19 +108,7 @@
|
|||
<div
|
||||
class="mb-2 flex h-10 w-10 items-center justify-center rounded-full bg-purple-100 dark:bg-purple-950"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-purple-600 dark:text-purple-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
<CloudArrowUp size={20} class="text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<h3 class="mb-1 font-semibold text-gray-900 dark:text-white">Maximale Größe</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Bis zu 10MB pro Bild</p>
|
||||
|
|
@ -155,19 +120,7 @@
|
|||
<div
|
||||
class="mb-2 flex h-10 w-10 items-center justify-center rounded-full bg-green-100 dark:bg-green-950"
|
||||
>
|
||||
<svg
|
||||
class="h-5 w-5 text-green-600 dark:text-green-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<CheckCircle size={20} class="text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<h3 class="mb-1 font-semibold text-gray-900 dark:text-white">Batch Upload</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
|
|
|
|||
|
|
@ -1,34 +1,16 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { RegisterPage, setGoogleClientId } from '@manacore/shared-auth-ui';
|
||||
import { RegisterPage } from '@manacore/shared-auth-ui';
|
||||
import { getRegisterTranslations } from '@manacore/shared-i18n';
|
||||
import PictureLogo from '$lib/components/branding/PictureLogo.svelte';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { PUBLIC_GOOGLE_CLIENT_ID, PUBLIC_APPLE_CLIENT_ID } from '$env/static/public';
|
||||
|
||||
// Default to German
|
||||
const translations = getRegisterTranslations('de');
|
||||
|
||||
onMount(() => {
|
||||
if (PUBLIC_GOOGLE_CLIENT_ID) {
|
||||
setGoogleClientId(PUBLIC_GOOGLE_CLIENT_ID);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSignUp(email: string, password: string) {
|
||||
return authStore.signUp(email, password);
|
||||
}
|
||||
|
||||
async function handleSignUpWithGoogle() {
|
||||
// TODO: Implement OAuth with Mana Core Auth when ready
|
||||
return { success: false, error: 'Google Sign-Up not yet implemented' };
|
||||
}
|
||||
|
||||
async function handleSignUpWithApple() {
|
||||
// TODO: Implement OAuth with Mana Core Auth when ready
|
||||
return { success: false, error: 'Apple Sign-Up not yet implemented' };
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -40,11 +22,7 @@
|
|||
logo={PictureLogo}
|
||||
primaryColor="#3b82f6"
|
||||
onSignUp={handleSignUp}
|
||||
onSignUpWithGoogle={PUBLIC_GOOGLE_CLIENT_ID ? handleSignUpWithGoogle : undefined}
|
||||
onSignUpWithApple={PUBLIC_APPLE_CLIENT_ID ? handleSignUpWithApple : undefined}
|
||||
{goto}
|
||||
enableGoogle={!!PUBLIC_GOOGLE_CLIENT_ID}
|
||||
enableApple={!!PUBLIC_APPLE_CLIENT_ID}
|
||||
successRedirect="/app/gallery"
|
||||
loginPath="/auth/login"
|
||||
lightBackground="#f0f9ff"
|
||||
|
|
|
|||
|
|
@ -16,14 +16,14 @@
|
|||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@types/node": "^20.0.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.0.0"
|
||||
|
|
@ -33,6 +33,8 @@
|
|||
"@manacore/shared-auth": "workspace:*",
|
||||
"@manacore/shared-auth-ui": "workspace:*",
|
||||
"@manacore/shared-branding": "workspace:*",
|
||||
"@manacore/shared-tailwind": "workspace:*",
|
||||
"@manacore/shared-theme": "workspace:*",
|
||||
"@manacore/shared-ui": "workspace:*",
|
||||
"lucide-svelte": "^0.460.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,44 +1,22 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@import "@manacore/shared-tailwind/themes.css";
|
||||
|
||||
:root {
|
||||
--color-bg: 250 250 250;
|
||||
--color-bg-secondary: 255 255 255;
|
||||
--color-text: 15 23 42;
|
||||
--color-text-secondary: 100 116 139;
|
||||
--color-border: 226 232 240;
|
||||
--color-primary: 14 165 233;
|
||||
}
|
||||
/* Scan shared packages for Tailwind classes */
|
||||
@source "../../../../packages/shared-ui/src";
|
||||
@source "../../../../packages/shared-auth-ui/src";
|
||||
@source "../../../../packages/shared-branding/src";
|
||||
|
||||
.dark {
|
||||
--color-bg: 15 23 42;
|
||||
--color-bg-secondary: 30 41 59;
|
||||
--color-text: 248 250 252;
|
||||
--color-text-secondary: 148 163 184;
|
||||
--color-border: 51 65 85;
|
||||
--color-primary: 56 189 248;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background-color: rgb(var(--color-bg));
|
||||
color: rgb(var(--color-text));
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Slide aspect ratio container */
|
||||
/* Presi-specific styles */
|
||||
.slide-container {
|
||||
aspect-ratio: 16 / 9;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Presentation mode fullscreen */
|
||||
.presentation-fullscreen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
background-color: rgb(var(--color-bg));
|
||||
background-color: hsl(var(--color-background));
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
|
|
@ -48,14 +26,14 @@ body {
|
|||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(var(--color-bg-secondary));
|
||||
background: hsl(var(--color-surface));
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgb(var(--color-border));
|
||||
background: hsl(var(--color-border));
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(var(--color-text-secondary));
|
||||
background: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
|
|
|||
10
apps/presi/apps/web/src/lib/stores/theme.ts
Normal file
10
apps/presi/apps/web/src/lib/stores/theme.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { createThemeStore } from '@manacore/shared-theme';
|
||||
|
||||
export const theme = createThemeStore({
|
||||
appId: 'presi',
|
||||
defaultVariant: 'stone',
|
||||
primaryColor: {
|
||||
light: '220 9% 46%',
|
||||
dark: '220 9% 56%',
|
||||
},
|
||||
});
|
||||
|
|
@ -3,8 +3,10 @@
|
|||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { PillNavigation } from '@manacore/shared-ui';
|
||||
import type { PillNavItem } from '@manacore/shared-ui';
|
||||
import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui';
|
||||
import { auth } from '$lib/stores/auth.svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
|
||||
import {
|
||||
isSidebarMode as sidebarModeStore,
|
||||
isNavCollapsed as collapsedStore,
|
||||
|
|
@ -16,7 +18,21 @@
|
|||
let loading = $state(true);
|
||||
let isSidebarMode = $state(false);
|
||||
let isCollapsed = $state(false);
|
||||
let isDark = $state(false);
|
||||
|
||||
// Theme variant dropdown items
|
||||
let themeVariantItems = $derived<PillDropdownItem[]>(
|
||||
theme.variants.map((variant) => ({
|
||||
id: variant,
|
||||
label: `${THEME_DEFINITIONS[variant].emoji} ${THEME_DEFINITIONS[variant].label}`,
|
||||
onClick: () => theme.setVariant(variant),
|
||||
active: theme.variant === variant,
|
||||
}))
|
||||
);
|
||||
|
||||
// Current theme variant label
|
||||
let currentThemeVariantLabel = $derived(
|
||||
`${THEME_DEFINITIONS[theme.variant].emoji} ${THEME_DEFINITIONS[theme.variant].label}`
|
||||
);
|
||||
|
||||
// Navigation items for Presi
|
||||
const navItems: PillNavItem[] = [
|
||||
|
|
@ -52,9 +68,7 @@
|
|||
}
|
||||
|
||||
function handleToggleTheme() {
|
||||
isDark = !isDark;
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
theme.toggleMode();
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
|
|
@ -74,10 +88,7 @@
|
|||
auth.init();
|
||||
|
||||
// Initialize theme
|
||||
isDark =
|
||||
localStorage.getItem('theme') === 'dark' ||
|
||||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.classList.toggle('dark', isDark);
|
||||
const cleanup = theme.initialize();
|
||||
|
||||
// Initialize sidebar mode from localStorage
|
||||
const savedSidebar = localStorage.getItem('presi-nav-sidebar');
|
||||
|
|
@ -94,6 +105,8 @@
|
|||
}
|
||||
|
||||
loading = false;
|
||||
|
||||
return cleanup;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -102,12 +115,12 @@
|
|||
</svelte:head>
|
||||
|
||||
{#if loading || auth.isLoading}
|
||||
<div class="flex min-h-screen items-center justify-center bg-slate-50 dark:bg-slate-900">
|
||||
<div class="flex min-h-screen items-center justify-center bg-background">
|
||||
<div class="text-center">
|
||||
<div
|
||||
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-primary-500 border-r-transparent"
|
||||
class="mb-4 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-primary border-r-transparent"
|
||||
></div>
|
||||
<p class="text-slate-500 dark:text-slate-400">Laden...</p>
|
||||
<p class="text-muted-foreground">Laden...</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if auth.isAuthenticated || publicRoutes.includes($page.url.pathname)}
|
||||
|
|
@ -121,16 +134,19 @@
|
|||
appName="Presi"
|
||||
homeRoute="/"
|
||||
onToggleTheme={handleToggleTheme}
|
||||
{isDark}
|
||||
isDark={theme.isDark}
|
||||
{isSidebarMode}
|
||||
onModeChange={handleModeChange}
|
||||
{isCollapsed}
|
||||
onCollapsedChange={handleCollapsedChange}
|
||||
showThemeToggle={true}
|
||||
showThemeVariants={true}
|
||||
{themeVariantItems}
|
||||
{currentThemeVariantLabel}
|
||||
showLanguageSwitcher={false}
|
||||
showLogout={true}
|
||||
onLogout={handleLogout}
|
||||
primaryColor="#0ea5e9"
|
||||
primaryColor="#64748b"
|
||||
/>
|
||||
|
||||
<!-- Main Content with dynamic padding based on nav mode -->
|
||||
|
|
@ -146,7 +162,7 @@
|
|||
</div>
|
||||
{:else}
|
||||
<!-- Public/Presentation routes without nav -->
|
||||
<main class="min-h-screen bg-slate-50 dark:bg-slate-900">
|
||||
<main class="min-h-screen bg-background">
|
||||
{@render children()}
|
||||
</main>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { auth } from '$lib/stores/auth.svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
import { User, Mail, Shield, LogOut, Sun, Moon, Monitor } from 'lucide-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
type ThemeMode = 'light' | 'dark' | 'system';
|
||||
let themeMode = $state<ThemeMode>('system');
|
||||
|
||||
onMount(() => {
|
||||
const saved = localStorage.getItem('theme') as ThemeMode | null;
|
||||
if (saved === 'light' || saved === 'dark') {
|
||||
themeMode = saved;
|
||||
} else {
|
||||
themeMode = 'system';
|
||||
}
|
||||
});
|
||||
|
||||
function setTheme(mode: ThemeMode) {
|
||||
themeMode = mode;
|
||||
if (mode === 'system') {
|
||||
localStorage.removeItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
document.documentElement.classList.toggle('dark', prefersDark);
|
||||
} else {
|
||||
localStorage.setItem('theme', mode);
|
||||
document.documentElement.classList.toggle('dark', mode === 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
auth.logout();
|
||||
|
|
@ -39,35 +15,35 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900 dark:text-white mb-8">Settings</h1>
|
||||
<h1 class="text-2xl font-bold text-foreground mb-8">Settings</h1>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Account Section -->
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden"
|
||||
class="bg-surface rounded-xl shadow-sm border border-border overflow-hidden"
|
||||
>
|
||||
<div class="p-4 border-b border-slate-200 dark:border-slate-700">
|
||||
<h2 class="text-lg font-semibold text-slate-900 dark:text-white flex items-center gap-2">
|
||||
<User class="w-5 h-5 text-slate-400" />
|
||||
<div class="p-4 border-b border-border">
|
||||
<h2 class="text-lg font-semibold text-foreground flex items-center gap-2">
|
||||
<User class="w-5 h-5 text-muted-foreground" />
|
||||
Account
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-4 space-y-4">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<Mail class="w-5 h-5 text-slate-400" />
|
||||
<Mail class="w-5 h-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Email</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">{auth.user?.email}</p>
|
||||
<p class="text-sm font-medium text-foreground">Email</p>
|
||||
<p class="text-sm text-muted-foreground">{auth.user?.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<Shield class="w-5 h-5 text-slate-400" />
|
||||
<Shield class="w-5 h-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">User ID</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400 font-mono">{auth.user?.id}</p>
|
||||
<p class="text-sm font-medium text-foreground">User ID</p>
|
||||
<p class="text-sm text-muted-foreground font-mono">{auth.user?.id}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -76,46 +52,46 @@
|
|||
|
||||
<!-- Appearance Section -->
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden"
|
||||
class="bg-surface rounded-xl shadow-sm border border-border overflow-hidden"
|
||||
>
|
||||
<div class="p-4 border-b border-slate-200 dark:border-slate-700">
|
||||
<h2 class="text-lg font-semibold text-slate-900 dark:text-white flex items-center gap-2">
|
||||
<Sun class="w-5 h-5 text-slate-400" />
|
||||
<div class="p-4 border-b border-border">
|
||||
<h2 class="text-lg font-semibold text-foreground flex items-center gap-2">
|
||||
<Sun class="w-5 h-5 text-muted-foreground" />
|
||||
Appearance
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400 mb-4">Choose your preferred theme</p>
|
||||
<p class="text-sm text-muted-foreground mb-4">Choose your preferred theme</p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<button
|
||||
onclick={() => setTheme('light')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {themeMode ===
|
||||
onclick={() => theme.setMode('light')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {theme.mode ===
|
||||
'light'
|
||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/30'
|
||||
: 'border-slate-200 dark:border-slate-600'}"
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border'}"
|
||||
>
|
||||
<Sun class="w-6 h-6 text-amber-500" />
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Light</span>
|
||||
<span class="text-sm font-medium text-foreground">Light</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => setTheme('dark')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {themeMode ===
|
||||
onclick={() => theme.setMode('dark')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {theme.mode ===
|
||||
'dark'
|
||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/30'
|
||||
: 'border-slate-200 dark:border-slate-600'}"
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border'}"
|
||||
>
|
||||
<Moon class="w-6 h-6 text-indigo-500" />
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">Dark</span>
|
||||
<span class="text-sm font-medium text-foreground">Dark</span>
|
||||
</button>
|
||||
<button
|
||||
onclick={() => setTheme('system')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {themeMode ===
|
||||
onclick={() => theme.setMode('system')}
|
||||
class="flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-colors {theme.mode ===
|
||||
'system'
|
||||
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/30'
|
||||
: 'border-slate-200 dark:border-slate-600'}"
|
||||
? 'border-primary bg-primary/10'
|
||||
: 'border-border'}"
|
||||
>
|
||||
<Monitor class="w-6 h-6 text-slate-500" />
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">System</span>
|
||||
<Monitor class="w-6 h-6 text-muted-foreground" />
|
||||
<span class="text-sm font-medium text-foreground">System</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -123,16 +99,16 @@
|
|||
|
||||
<!-- Danger Zone -->
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-red-200 dark:border-red-900/50 overflow-hidden"
|
||||
class="bg-surface rounded-xl shadow-sm border border-red-300 dark:border-red-900/50 overflow-hidden"
|
||||
>
|
||||
<div class="p-4 border-b border-red-200 dark:border-red-900/50 bg-red-50 dark:bg-red-900/20">
|
||||
<div class="p-4 border-b border-red-300 dark:border-red-900/50 bg-red-50 dark:bg-red-900/20">
|
||||
<h2 class="text-lg font-semibold text-red-700 dark:text-red-400">Danger Zone</h2>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Sign out</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
<p class="text-sm font-medium text-foreground">Sign out</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Sign out of your account on this device
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
import type { Config } from 'tailwindcss';
|
||||
|
||||
export default {
|
||||
content: [
|
||||
'./src/**/*.{html,js,svelte,ts}',
|
||||
'../../../packages/shared-ui/src/**/*.{html,js,svelte,ts}',
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e',
|
||||
950: '#082f49',
|
||||
},
|
||||
},
|
||||
aspectRatio: {
|
||||
'16/9': '16 / 9',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
} satisfies Config;
|
||||
|
|
@ -20,9 +20,19 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
ssr: {
|
||||
noExternal: ['@manacore/shared-ui'],
|
||||
noExternal: [
|
||||
'@manacore/shared-ui',
|
||||
'@manacore/shared-theme',
|
||||
'@manacore/shared-auth-ui',
|
||||
'@manacore/shared-branding',
|
||||
],
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['@manacore/shared-ui'],
|
||||
exclude: [
|
||||
'@manacore/shared-ui',
|
||||
'@manacore/shared-theme',
|
||||
'@manacore/shared-auth-ui',
|
||||
'@manacore/shared-branding',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,22 @@
|
|||
"./ThemeModeSelector.svelte": {
|
||||
"svelte": "./src/ThemeModeSelector.svelte",
|
||||
"default": "./src/ThemeModeSelector.svelte"
|
||||
},
|
||||
"./ThemeColorPreview.svelte": {
|
||||
"svelte": "./src/components/ThemeColorPreview.svelte",
|
||||
"default": "./src/components/ThemeColorPreview.svelte"
|
||||
},
|
||||
"./ThemeCard.svelte": {
|
||||
"svelte": "./src/components/ThemeCard.svelte",
|
||||
"default": "./src/components/ThemeCard.svelte"
|
||||
},
|
||||
"./ThemeGrid.svelte": {
|
||||
"svelte": "./src/components/ThemeGrid.svelte",
|
||||
"default": "./src/components/ThemeGrid.svelte"
|
||||
},
|
||||
"./ThemePage.svelte": {
|
||||
"svelte": "./src/pages/ThemePage.svelte",
|
||||
"default": "./src/pages/ThemePage.svelte"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
136
packages/shared-theme-ui/src/components/ThemeCard.svelte
Normal file
136
packages/shared-theme-ui/src/components/ThemeCard.svelte
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<script lang="ts">
|
||||
import type { ThemeVariant } from '@manacore/shared-theme';
|
||||
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
|
||||
import { Check, Lock, Clock, Star } from '@manacore/shared-icons';
|
||||
import type { ThemeStatus } from '../types';
|
||||
import ThemeColorPreview from './ThemeColorPreview.svelte';
|
||||
|
||||
interface Props {
|
||||
variant: ThemeVariant;
|
||||
isActive: boolean;
|
||||
status?: ThemeStatus;
|
||||
onClick?: () => void;
|
||||
onUnlock?: () => void;
|
||||
translations?: {
|
||||
locked?: string;
|
||||
comingSoon?: string;
|
||||
premium?: string;
|
||||
unlock?: string;
|
||||
lightPreview?: string;
|
||||
darkPreview?: string;
|
||||
};
|
||||
}
|
||||
|
||||
let {
|
||||
variant,
|
||||
isActive,
|
||||
status = 'available',
|
||||
onClick,
|
||||
onUnlock,
|
||||
translations = {},
|
||||
}: Props = $props();
|
||||
|
||||
const t = {
|
||||
locked: translations.locked ?? 'Gesperrt',
|
||||
comingSoon: translations.comingSoon ?? 'Bald verfügbar',
|
||||
premium: translations.premium ?? 'Premium',
|
||||
unlock: translations.unlock ?? 'Freischalten',
|
||||
lightPreview: translations.lightPreview ?? 'Hell',
|
||||
darkPreview: translations.darkPreview ?? 'Dunkel',
|
||||
};
|
||||
|
||||
const definition = $derived(THEME_DEFINITIONS[variant]);
|
||||
const isAvailable = $derived(status === 'available');
|
||||
const isLocked = $derived(status === 'locked');
|
||||
const isComingSoon = $derived(status === 'coming_soon');
|
||||
const isPremium = $derived(status === 'premium');
|
||||
|
||||
function handleClick() {
|
||||
if (isAvailable && onClick) {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
|
||||
function handleUnlock(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
if (onUnlock) {
|
||||
onUnlock();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleClick}
|
||||
disabled={!isAvailable}
|
||||
class="relative w-full p-4 rounded-xl border-2 transition-all text-left
|
||||
{isActive
|
||||
? 'border-primary bg-primary/5 ring-2 ring-primary/20'
|
||||
: 'border-border bg-surface hover:border-primary/50'}
|
||||
{!isAvailable ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'}
|
||||
{isPremium ? 'border-yellow-500/50' : ''}"
|
||||
>
|
||||
<!-- Premium badge -->
|
||||
{#if isPremium}
|
||||
<div
|
||||
class="absolute -top-2 -right-2 flex items-center gap-1 px-2 py-0.5
|
||||
bg-yellow-500 text-yellow-950 text-xs font-medium rounded-full"
|
||||
>
|
||||
<Star size={12} weight="fill" />
|
||||
{t.premium}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Active checkmark -->
|
||||
{#if isActive && isAvailable}
|
||||
<div
|
||||
class="absolute top-3 right-3 w-6 h-6 flex items-center justify-center
|
||||
bg-primary text-primary-foreground rounded-full"
|
||||
>
|
||||
<Check size={14} weight="bold" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xl">{definition.emoji}</span>
|
||||
<span class="font-semibold text-foreground">{definition.label}</span>
|
||||
</div>
|
||||
|
||||
<!-- Color previews -->
|
||||
<div class="space-y-2 mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-muted-foreground w-10">{t.lightPreview}</span>
|
||||
<ThemeColorPreview {variant} mode="light" size="sm" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-muted-foreground w-10">{t.darkPreview}</span>
|
||||
<ThemeColorPreview {variant} mode="dark" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status badges -->
|
||||
{#if isLocked}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-1 text-muted-foreground text-sm">
|
||||
<Lock size={14} weight="bold" />
|
||||
{t.locked}
|
||||
</div>
|
||||
{#if onUnlock}
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleUnlock}
|
||||
class="px-2 py-1 text-xs font-medium text-primary bg-primary/10
|
||||
rounded hover:bg-primary/20 transition-colors"
|
||||
>
|
||||
{t.unlock}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if isComingSoon}
|
||||
<div class="flex items-center gap-1 text-muted-foreground text-sm">
|
||||
<Clock size={14} weight="bold" />
|
||||
{t.comingSoon}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<script lang="ts">
|
||||
import type { ThemeVariant } from '@manacore/shared-theme';
|
||||
import { THEME_DEFINITIONS } from '@manacore/shared-theme';
|
||||
|
||||
interface Props {
|
||||
variant: ThemeVariant;
|
||||
mode: 'light' | 'dark';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
let { variant, mode, size = 'md' }: Props = $props();
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'w-4 h-4',
|
||||
md: 'w-5 h-5',
|
||||
lg: 'w-6 h-6',
|
||||
};
|
||||
|
||||
const gapClasses = {
|
||||
sm: '-ml-1.5',
|
||||
md: '-ml-2',
|
||||
lg: '-ml-2.5',
|
||||
};
|
||||
|
||||
const colors = $derived(() => {
|
||||
const def = THEME_DEFINITIONS[variant];
|
||||
const themeColors = mode === 'dark' ? def.dark : def.light;
|
||||
return [
|
||||
{ name: 'primary', value: themeColors.primary },
|
||||
{ name: 'secondary', value: themeColors.secondary },
|
||||
{ name: 'background', value: themeColors.background },
|
||||
{ name: 'surface', value: themeColors.surface },
|
||||
{ name: 'muted', value: themeColors.muted },
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex items-center">
|
||||
{#each colors() as color, i}
|
||||
<div
|
||||
class="{sizeClasses[size]} rounded-full border border-white/20 shadow-sm {i > 0
|
||||
? gapClasses[size]
|
||||
: ''}"
|
||||
style="background-color: hsl({color.value}); z-index: {5 - i};"
|
||||
title={color.name}
|
||||
></div>
|
||||
{/each}
|
||||
</div>
|
||||
58
packages/shared-theme-ui/src/components/ThemeGrid.svelte
Normal file
58
packages/shared-theme-ui/src/components/ThemeGrid.svelte
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
import type { ThemeVariant } from '@manacore/shared-theme';
|
||||
import { THEME_VARIANTS } from '@manacore/shared-theme';
|
||||
import type { ThemeCardData, ThemePageTranslations } from '../types';
|
||||
import ThemeCard from './ThemeCard.svelte';
|
||||
|
||||
interface Props {
|
||||
currentVariant: ThemeVariant;
|
||||
onSelect: (variant: ThemeVariant) => void;
|
||||
themes?: ThemeCardData[];
|
||||
onUnlock?: (variant: ThemeVariant) => void;
|
||||
showLockedThemes?: boolean;
|
||||
translations?: Partial<ThemePageTranslations>;
|
||||
}
|
||||
|
||||
let {
|
||||
currentVariant,
|
||||
onSelect,
|
||||
themes,
|
||||
onUnlock,
|
||||
showLockedThemes = true,
|
||||
translations = {},
|
||||
}: Props = $props();
|
||||
|
||||
// Build theme data - use provided themes or create defaults from THEME_VARIANTS
|
||||
const themeData = $derived(() => {
|
||||
if (themes) {
|
||||
return showLockedThemes ? themes : themes.filter((t) => t.status === 'available');
|
||||
}
|
||||
// Default: all variants are available
|
||||
return THEME_VARIANTS.map((variant) => ({
|
||||
variant,
|
||||
status: 'available' as const,
|
||||
}));
|
||||
});
|
||||
|
||||
const cardTranslations = $derived({
|
||||
locked: translations.locked,
|
||||
comingSoon: translations.comingSoon,
|
||||
premium: translations.premium,
|
||||
unlock: translations.unlock,
|
||||
lightPreview: translations.lightPreview,
|
||||
darkPreview: translations.darkPreview,
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{#each themeData() as theme (theme.variant)}
|
||||
<ThemeCard
|
||||
variant={theme.variant}
|
||||
isActive={currentVariant === theme.variant}
|
||||
status={theme.status}
|
||||
onClick={() => onSelect(theme.variant)}
|
||||
onUnlock={onUnlock ? () => onUnlock(theme.variant) : undefined}
|
||||
translations={cardTranslations}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -1,4 +1,21 @@
|
|||
// Theme UI Components
|
||||
// Theme UI Components (existing)
|
||||
export { default as ThemeToggle } from './ThemeToggle.svelte';
|
||||
export { default as ThemeSelector } from './ThemeSelector.svelte';
|
||||
export { default as ThemeModeSelector } from './ThemeModeSelector.svelte';
|
||||
|
||||
// New Components
|
||||
export { default as ThemeColorPreview } from './components/ThemeColorPreview.svelte';
|
||||
export { default as ThemeCard } from './components/ThemeCard.svelte';
|
||||
export { default as ThemeGrid } from './components/ThemeGrid.svelte';
|
||||
|
||||
// Pages
|
||||
export { default as ThemePage } from './pages/ThemePage.svelte';
|
||||
|
||||
// Types
|
||||
export type {
|
||||
ThemeStatus,
|
||||
ThemeCardData,
|
||||
ThemePageProps,
|
||||
ThemePageTranslations,
|
||||
} from './types';
|
||||
export { defaultTranslations } from './types';
|
||||
|
|
|
|||
125
packages/shared-theme-ui/src/pages/ThemePage.svelte
Normal file
125
packages/shared-theme-ui/src/pages/ThemePage.svelte
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<script lang="ts">
|
||||
import type { ThemeVariant, ThemeMode } from '@manacore/shared-theme';
|
||||
import { ArrowLeft, Sun, Moon, Desktop } from '@manacore/shared-icons';
|
||||
import type { ThemeCardData, ThemePageTranslations } from '../types';
|
||||
import { defaultTranslations } from '../types';
|
||||
import ThemeGrid from '../components/ThemeGrid.svelte';
|
||||
|
||||
interface Props {
|
||||
// Theme Store Integration
|
||||
currentVariant: ThemeVariant;
|
||||
onSelectTheme: (variant: ThemeVariant) => void;
|
||||
|
||||
// Theme Data (for store extension)
|
||||
themes?: ThemeCardData[];
|
||||
|
||||
// UI Customization
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
showModeSelector?: boolean;
|
||||
currentMode?: ThemeMode;
|
||||
onModeChange?: (mode: ThemeMode) => void;
|
||||
|
||||
// Back navigation
|
||||
showBackButton?: boolean;
|
||||
onBack?: () => void;
|
||||
|
||||
// Store Features (preparation)
|
||||
showLockedThemes?: boolean;
|
||||
onUnlockTheme?: (variant: ThemeVariant) => void;
|
||||
|
||||
// Translations
|
||||
translations?: Partial<ThemePageTranslations>;
|
||||
}
|
||||
|
||||
let {
|
||||
currentVariant,
|
||||
onSelectTheme,
|
||||
themes,
|
||||
title,
|
||||
subtitle,
|
||||
showModeSelector = false,
|
||||
currentMode = 'system',
|
||||
onModeChange,
|
||||
showBackButton = false,
|
||||
onBack,
|
||||
showLockedThemes = true,
|
||||
onUnlockTheme,
|
||||
translations = {},
|
||||
}: Props = $props();
|
||||
|
||||
// Merge translations with defaults
|
||||
const t = $derived({ ...defaultTranslations, ...translations });
|
||||
|
||||
const modes: { mode: ThemeMode; icon: typeof Sun; label: string }[] = $derived([
|
||||
{ mode: 'light', icon: Sun, label: t.lightMode },
|
||||
{ mode: 'dark', icon: Moon, label: t.darkMode },
|
||||
{ mode: 'system', icon: Desktop, label: t.systemMode },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-background">
|
||||
<div class="max-w-4xl mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<header class="mb-8">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
{#if showBackButton && onBack}
|
||||
<button
|
||||
type="button"
|
||||
onclick={onBack}
|
||||
class="p-2 -ml-2 text-muted-foreground hover:text-foreground
|
||||
hover:bg-muted rounded-lg transition-colors"
|
||||
aria-label="Zurück"
|
||||
>
|
||||
<ArrowLeft size={20} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
<h1 class="text-2xl font-bold text-foreground">
|
||||
{title ?? t.title}
|
||||
</h1>
|
||||
</div>
|
||||
<p class="text-muted-foreground">
|
||||
{subtitle ?? t.subtitle}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- Mode Selector -->
|
||||
{#if showModeSelector && onModeChange}
|
||||
<section class="mb-8">
|
||||
<h2 class="text-sm font-medium text-muted-foreground mb-3">
|
||||
{t.modeLabel}
|
||||
</h2>
|
||||
<div class="inline-flex rounded-lg bg-muted p-1">
|
||||
{#each modes as { mode, icon: Icon, label }}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => onModeChange(mode)}
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium transition-colors
|
||||
{currentMode === mode
|
||||
? 'bg-surface text-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'}"
|
||||
>
|
||||
<Icon size={16} weight={currentMode === mode ? 'fill' : 'regular'} />
|
||||
{label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Theme Grid -->
|
||||
<section>
|
||||
<h2 class="text-sm font-medium text-muted-foreground mb-4">
|
||||
{t.currentTheme}
|
||||
</h2>
|
||||
<ThemeGrid
|
||||
{currentVariant}
|
||||
onSelect={onSelectTheme}
|
||||
{themes}
|
||||
onUnlock={onUnlockTheme}
|
||||
{showLockedThemes}
|
||||
{translations}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
87
packages/shared-theme-ui/src/types.ts
Normal file
87
packages/shared-theme-ui/src/types.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import type { ThemeVariant, ThemeMode } from '@manacore/shared-theme';
|
||||
|
||||
/**
|
||||
* Theme availability status for store integration
|
||||
*/
|
||||
export type ThemeStatus = 'available' | 'locked' | 'coming_soon' | 'premium';
|
||||
|
||||
/**
|
||||
* Theme card data for displaying in grid/list
|
||||
*/
|
||||
export interface ThemeCardData {
|
||||
variant: ThemeVariant;
|
||||
status: ThemeStatus;
|
||||
isPremium?: boolean;
|
||||
price?: number;
|
||||
releaseDate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for ThemePage component
|
||||
*/
|
||||
export interface ThemePageProps {
|
||||
// Theme Store Integration
|
||||
currentVariant: ThemeVariant;
|
||||
onSelectTheme: (variant: ThemeVariant) => void;
|
||||
|
||||
// Theme Data (for store extension)
|
||||
themes?: ThemeCardData[];
|
||||
|
||||
// UI Customization
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
showModeSelector?: boolean;
|
||||
currentMode?: ThemeMode;
|
||||
onModeChange?: (mode: ThemeMode) => void;
|
||||
|
||||
// Back navigation
|
||||
showBackButton?: boolean;
|
||||
onBack?: () => void;
|
||||
|
||||
// Store Features (preparation)
|
||||
showLockedThemes?: boolean;
|
||||
onUnlockTheme?: (variant: ThemeVariant) => void;
|
||||
|
||||
// Translations
|
||||
translations?: Partial<ThemePageTranslations>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translations for ThemePage
|
||||
*/
|
||||
export interface ThemePageTranslations {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
modeLabel: string;
|
||||
lightMode: string;
|
||||
darkMode: string;
|
||||
systemMode: string;
|
||||
currentTheme: string;
|
||||
selectTheme: string;
|
||||
locked: string;
|
||||
comingSoon: string;
|
||||
premium: string;
|
||||
unlock: string;
|
||||
lightPreview: string;
|
||||
darkPreview: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default German translations
|
||||
*/
|
||||
export const defaultTranslations: ThemePageTranslations = {
|
||||
title: 'Theme-Einstellungen',
|
||||
subtitle: 'Wähle dein bevorzugtes Farbschema',
|
||||
modeLabel: 'Modus',
|
||||
lightMode: 'Hell',
|
||||
darkMode: 'Dunkel',
|
||||
systemMode: 'System',
|
||||
currentTheme: 'Aktuelles Theme',
|
||||
selectTheme: 'Auswählen',
|
||||
locked: 'Gesperrt',
|
||||
comingSoon: 'Bald verfügbar',
|
||||
premium: 'Premium',
|
||||
unlock: 'Freischalten',
|
||||
lightPreview: 'Hell',
|
||||
darkPreview: 'Dunkel',
|
||||
};
|
||||
|
|
@ -31,7 +31,7 @@ const lumeLight: ThemeColors = {
|
|||
primaryForeground: '0 0% 0%', // Black text on gold
|
||||
secondary: '47 100% 41%', // #D4B200 - Darker gold
|
||||
secondaryForeground: '0 0% 0%',
|
||||
background: '0 0% 87%', // #dddddd - Light gray
|
||||
background: '45 30% 96%', // Warm cream with gold tint
|
||||
foreground: '0 0% 17%', // #2c2c2c - Dark text
|
||||
surface: '0 0% 100%', // #ffffff - White
|
||||
surfaceHover: '0 0% 96%', // #f5f5f5
|
||||
|
|
@ -52,15 +52,15 @@ const lumeDark: ThemeColors = {
|
|||
primaryForeground: '0 0% 0%', // Black text on gold
|
||||
secondary: '47 70% 29%', // #7C6B16 - Muted gold
|
||||
secondaryForeground: '0 0% 100%',
|
||||
background: '0 0% 6%', // #101010 - Very dark
|
||||
background: '40 10% 7%', // Very dark with warm tint
|
||||
foreground: '0 0% 100%', // #ffffff - White text
|
||||
surface: '0 0% 12%', // #1E1E1E - Dark surface
|
||||
surfaceHover: '0 0% 16%', // #292929
|
||||
surfaceElevated: '0 0% 14%', // #242424
|
||||
muted: '0 0% 20%', // #333333
|
||||
mutedForeground: '0 0% 60%', // #999999
|
||||
border: '0 0% 26%', // #424242
|
||||
borderStrong: '0 0% 35%', // #595959
|
||||
surface: '40 8% 12%', // Dark surface with warm tint
|
||||
surfaceHover: '40 8% 16%',
|
||||
surfaceElevated: '40 8% 14%',
|
||||
muted: '40 6% 20%',
|
||||
mutedForeground: '40 5% 60%',
|
||||
border: '40 6% 26%',
|
||||
borderStrong: '40 5% 35%',
|
||||
error: '6 78% 57%', // #e74c3c
|
||||
success: '145 63% 49%', // #2ecc71
|
||||
warning: '48 100% 50%', // #f1c40f
|
||||
|
|
@ -94,15 +94,15 @@ const natureDark: ThemeColors = {
|
|||
primaryForeground: '0 0% 100%',
|
||||
secondary: '122 30% 35%', // Muted green
|
||||
secondaryForeground: '0 0% 100%',
|
||||
background: '0 0% 7%', // #121212
|
||||
background: '120 12% 6%', // Very dark with green tint
|
||||
foreground: '0 0% 100%', // White
|
||||
surface: '120 10% 12%', // Slight green tint
|
||||
surfaceHover: '120 10% 16%',
|
||||
surfaceElevated: '120 10% 14%',
|
||||
muted: '120 10% 20%',
|
||||
surface: '120 10% 11%', // Slight green tint
|
||||
surfaceHover: '120 10% 15%',
|
||||
surfaceElevated: '120 10% 13%',
|
||||
muted: '120 10% 19%',
|
||||
mutedForeground: '120 10% 60%',
|
||||
border: '120 10% 25%',
|
||||
borderStrong: '120 10% 35%',
|
||||
border: '120 10% 24%',
|
||||
borderStrong: '120 10% 34%',
|
||||
error: '0 65% 57%',
|
||||
success: '122 50% 55%',
|
||||
warning: '48 100% 50%',
|
||||
|
|
@ -136,11 +136,11 @@ const stoneDark: ThemeColors = {
|
|||
primaryForeground: '0 0% 0%',
|
||||
secondary: '200 12% 35%',
|
||||
secondaryForeground: '0 0% 100%',
|
||||
background: '0 0% 7%', // #121212
|
||||
background: '210 15% 8%', // Very dark with blue-gray tint
|
||||
foreground: '0 0% 100%',
|
||||
surface: '200 10% 12%',
|
||||
surfaceHover: '200 10% 16%',
|
||||
surfaceElevated: '200 10% 14%',
|
||||
surface: '200 12% 12%',
|
||||
surfaceHover: '200 12% 16%',
|
||||
surfaceElevated: '200 12% 14%',
|
||||
muted: '200 10% 20%',
|
||||
mutedForeground: '200 10% 60%',
|
||||
border: '200 10% 25%',
|
||||
|
|
@ -178,15 +178,15 @@ const oceanDark: ThemeColors = {
|
|||
primaryForeground: '0 0% 0%',
|
||||
secondary: '199 60% 35%',
|
||||
secondaryForeground: '0 0% 100%',
|
||||
background: '0 0% 7%', // #121212
|
||||
background: '200 25% 7%', // Very dark with blue tint
|
||||
foreground: '0 0% 100%',
|
||||
surface: '199 30% 12%', // Slight blue tint
|
||||
surfaceHover: '199 30% 16%',
|
||||
surfaceElevated: '199 30% 14%',
|
||||
muted: '199 20% 20%',
|
||||
mutedForeground: '199 20% 60%',
|
||||
border: '199 20% 25%',
|
||||
borderStrong: '199 20% 35%',
|
||||
surface: '199 20% 11%', // Slight blue tint
|
||||
surfaceHover: '199 20% 15%',
|
||||
surfaceElevated: '199 20% 13%',
|
||||
muted: '199 15% 19%',
|
||||
mutedForeground: '199 15% 60%',
|
||||
border: '199 15% 24%',
|
||||
borderStrong: '199 15% 34%',
|
||||
error: '4 90% 58%',
|
||||
success: '145 63% 49%',
|
||||
warning: '48 100% 50%',
|
||||
|
|
|
|||
|
|
@ -219,4 +219,12 @@ export const APP_THEME_CONFIGS = {
|
|||
dark: '280 60% 60%' as HSLValue,
|
||||
},
|
||||
},
|
||||
picture: {
|
||||
appId: 'picture',
|
||||
defaultVariant: 'ocean' as ThemeVariant,
|
||||
primaryColor: {
|
||||
light: '217 91% 60%' as HSLValue, // Blue #3b82f6
|
||||
dark: '217 91% 60%' as HSLValue,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@
|
|||
chevronDown: 'M19 9l-7 7-7-7',
|
||||
globe:
|
||||
'M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9',
|
||||
palette:
|
||||
'M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01',
|
||||
};
|
||||
|
||||
function getIcon(iconName: string) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@
|
|||
elements?: PillNavElement[];
|
||||
/** Show logout button */
|
||||
showLogout?: boolean;
|
||||
/** Theme variant dropdown items */
|
||||
themeVariantItems?: PillDropdownItem[];
|
||||
/** Current theme variant label */
|
||||
currentThemeVariantLabel?: string;
|
||||
/** Show theme variant selector */
|
||||
showThemeVariants?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
|
|
@ -65,6 +71,9 @@
|
|||
primaryColor,
|
||||
elements = [],
|
||||
showLogout = true,
|
||||
themeVariantItems = [],
|
||||
currentThemeVariantLabel = 'Theme',
|
||||
showThemeVariants = false,
|
||||
}: Props = $props();
|
||||
|
||||
// Type guards for elements
|
||||
|
|
@ -161,6 +170,8 @@
|
|||
grid: 'M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z',
|
||||
gridSmall:
|
||||
'M4 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM10 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM16 5a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1V5zM4 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1v-2zM10 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2zM16 11a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 01-1 1h-2a1 1 0 01-1-1v-2z',
|
||||
palette:
|
||||
'M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01',
|
||||
};
|
||||
|
||||
function getIconPath(name: string): string {
|
||||
|
|
@ -297,6 +308,16 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Theme Variant Selector -->
|
||||
{#if showThemeVariants && themeVariantItems.length > 0}
|
||||
<PillDropdown
|
||||
items={themeVariantItems}
|
||||
direction="down"
|
||||
label={currentThemeVariantLabel}
|
||||
icon="palette"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Theme Toggle -->
|
||||
{#if showThemeToggle && onToggleTheme}
|
||||
<button
|
||||
|
|
|
|||
342
pnpm-lock.yaml
generated
342
pnpm-lock.yaml
generated
|
|
@ -73,7 +73,7 @@ importers:
|
|||
devDependencies:
|
||||
'@nestjs/cli':
|
||||
specifier: ^10.4.9
|
||||
version: 10.4.9(esbuild@0.27.0)
|
||||
version: 10.4.9(esbuild@0.19.12)
|
||||
'@nestjs/schematics':
|
||||
specifier: ^10.2.3
|
||||
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
|
||||
|
|
@ -106,7 +106,7 @@ importers:
|
|||
version: 0.5.21
|
||||
ts-loader:
|
||||
specifier: ^9.5.1
|
||||
version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0))
|
||||
version: 9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12))
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
|
||||
|
|
@ -133,14 +133,14 @@ importers:
|
|||
version: link:../../../../packages/shared-landing-ui
|
||||
astro:
|
||||
specifier: ^5.16.0
|
||||
version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
|
||||
version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
devDependencies:
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
|
||||
version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.19(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))
|
||||
|
|
@ -1386,6 +1386,9 @@ importers:
|
|||
'@manacore/shared-i18n':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-i18n
|
||||
'@manacore/shared-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-icons
|
||||
'@manacore/shared-subscription-types':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-subscription-types
|
||||
|
|
@ -1395,6 +1398,9 @@ importers:
|
|||
'@manacore/shared-tailwind':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-tailwind
|
||||
'@manacore/shared-theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-theme
|
||||
'@manacore/shared-theme-ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-theme-ui
|
||||
|
|
@ -1722,6 +1728,12 @@ importers:
|
|||
'@manacore/shared-branding':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-branding
|
||||
'@manacore/shared-tailwind':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-tailwind
|
||||
'@manacore/shared-theme':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-theme
|
||||
'@manacore/shared-ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../../../packages/shared-ui
|
||||
|
|
@ -1741,12 +1753,12 @@ importers:
|
|||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^5.0.0
|
||||
version: 5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.19.25
|
||||
autoprefixer:
|
||||
specifier: ^10.4.16
|
||||
version: 10.4.22(postcss@8.5.6)
|
||||
postcss:
|
||||
specifier: ^8.4.32
|
||||
version: 8.5.6
|
||||
|
|
@ -1763,8 +1775,8 @@ importers:
|
|||
specifier: ^4.0.0
|
||||
version: 4.3.4(picomatch@4.0.3)(svelte@5.44.0)(typescript@5.9.3)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.18(tsx@4.20.6)(yaml@2.8.1)
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
tslib:
|
||||
specifier: ^2.4.1
|
||||
version: 2.8.1
|
||||
|
|
@ -1831,7 +1843,7 @@ importers:
|
|||
devDependencies:
|
||||
'@nestjs/cli':
|
||||
specifier: ^10.4.9
|
||||
version: 10.4.9(esbuild@0.19.12)
|
||||
version: 10.4.9(esbuild@0.27.0)
|
||||
'@nestjs/schematics':
|
||||
specifier: ^10.2.3
|
||||
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
|
||||
|
|
@ -1864,7 +1876,7 @@ importers:
|
|||
version: 0.5.21
|
||||
ts-loader:
|
||||
specifier: ^9.5.1
|
||||
version: 9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12))
|
||||
version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0))
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
|
||||
|
|
@ -2545,6 +2557,9 @@ importers:
|
|||
'@manacore/shared-auth':
|
||||
specifier: workspace:*
|
||||
version: link:../shared-auth
|
||||
'@manacore/shared-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../shared-icons
|
||||
devDependencies:
|
||||
svelte:
|
||||
specifier: ^5.16.0
|
||||
|
|
@ -4771,7 +4786,7 @@ packages:
|
|||
|
||||
'@expo/bunyan@4.0.1':
|
||||
resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
engines: {'0': node >=0.10.0}
|
||||
|
||||
'@expo/cli@0.22.26':
|
||||
resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==}
|
||||
|
|
@ -17096,6 +17111,16 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
|
||||
autoprefixer: 10.4.22(postcss@8.5.6)
|
||||
postcss: 8.5.6
|
||||
postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
|
||||
tailwindcss: 3.4.18(tsx@4.20.6)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
|
||||
|
|
@ -18997,7 +19022,7 @@ snapshots:
|
|||
wrap-ansi: 7.0.0
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
expo-router: 6.0.15(jiucxy5ca3jdtbnulaxuc46jdq)
|
||||
expo-router: 6.0.15(5e7ih2rh6mb55wruwvjljgzihq)
|
||||
react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
|
|
@ -19074,7 +19099,7 @@ snapshots:
|
|||
wrap-ansi: 7.0.0
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
expo-router: 6.0.15(dux2nvtiztnejw7mxzfaajqvh4)
|
||||
expo-router: 6.0.15(nttrd3tw67nnyhowcwgdzipb5e)
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
|
|
@ -20382,6 +20407,43 @@ snapshots:
|
|||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@jest/console': 30.2.0
|
||||
'@jest/pattern': 30.0.1
|
||||
'@jest/reporters': 30.2.0
|
||||
'@jest/test-result': 30.2.0
|
||||
'@jest/transform': 30.2.0
|
||||
'@jest/types': 30.2.0
|
||||
'@types/node': 22.19.1
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
ci-info: 4.3.1
|
||||
exit-x: 0.2.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-changed-files: 30.2.0
|
||||
jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
jest-haste-map: 30.2.0
|
||||
jest-message-util: 30.2.0
|
||||
jest-regex-util: 30.0.1
|
||||
jest-resolve: 30.2.0
|
||||
jest-resolve-dependencies: 30.2.0
|
||||
jest-runner: 30.2.0
|
||||
jest-runtime: 30.2.0
|
||||
jest-snapshot: 30.2.0
|
||||
jest-util: 30.2.0
|
||||
jest-validate: 30.2.0
|
||||
jest-watcher: 30.2.0
|
||||
micromatch: 4.0.8
|
||||
pretty-format: 30.2.0
|
||||
slash: 3.0.0
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- esbuild-register
|
||||
- supports-color
|
||||
- ts-node
|
||||
optional: true
|
||||
|
||||
'@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))':
|
||||
dependencies:
|
||||
'@jest/console': 30.2.0
|
||||
|
|
@ -23305,17 +23367,17 @@ snapshots:
|
|||
react-test-renderer: 19.1.0(react@19.1.0)
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
'@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
jest-matcher-utils: 30.2.0
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 30.2.0
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
|
||||
react-test-renderer: 19.1.0(react@19.1.0)
|
||||
redent: 3.0.0
|
||||
optionalDependencies:
|
||||
jest: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
|
||||
jest: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
optional: true
|
||||
|
||||
'@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
|
|
@ -24277,11 +24339,11 @@ snapshots:
|
|||
- vite
|
||||
optional: true
|
||||
|
||||
'@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)':
|
||||
'@vitest/browser@3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
'@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
'@vitest/utils': 3.2.4
|
||||
magic-string: 0.30.21
|
||||
sirv: 3.0.2
|
||||
|
|
@ -24321,6 +24383,15 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
optional: true
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
tinyrainbow: 2.0.0
|
||||
|
|
@ -24350,7 +24421,7 @@ snapshots:
|
|||
sirv: 3.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
||||
'@vitest/utils@3.2.4':
|
||||
dependencies:
|
||||
|
|
@ -24944,6 +25015,108 @@ snapshots:
|
|||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.0
|
||||
'@astrojs/internal-helpers': 0.7.5
|
||||
'@astrojs/markdown-remark': 6.3.9
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 3.0.1
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.53.3)
|
||||
acorn: 8.15.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.3.1
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.0
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.5.0
|
||||
diff: 5.2.0
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.25.12
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.3.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.1
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.5.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.3
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.3
|
||||
shiki: 3.15.0
|
||||
smol-toml: 1.5.2
|
||||
svgo: 4.0.0
|
||||
tinyexec: 1.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.6.0
|
||||
unist-util-visit: 5.0.0
|
||||
unstorage: 1.17.3(@netlify/blobs@10.4.1)(ioredis@5.8.2)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.1(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
vitefu: 1.1.1(vite@6.4.1(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.0(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@24.10.1)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.0
|
||||
|
|
@ -26903,9 +27076,9 @@ snapshots:
|
|||
'@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-expo: 1.0.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
|
||||
globals: 16.5.0
|
||||
|
|
@ -26920,9 +27093,9 @@ snapshots:
|
|||
'@typescript-eslint/eslint-plugin': 8.48.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-expo: 0.1.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
|
||||
globals: 16.5.0
|
||||
|
|
@ -26992,7 +27165,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
|
|
@ -27003,7 +27176,22 @@ snapshots:
|
|||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
get-tsconfig: 4.13.0
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -27027,25 +27215,25 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -27145,7 +27333,7 @@ snapshots:
|
|||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
|
@ -27156,7 +27344,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -27174,7 +27362,7 @@ snapshots:
|
|||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
|
@ -27185,7 +27373,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -28326,21 +28514,21 @@ snapshots:
|
|||
- '@types/react-dom'
|
||||
- supports-color
|
||||
|
||||
expo-router@6.0.15(jiucxy5ca3jdtbnulaxuc46jdq):
|
||||
expo-router@6.0.15(nttrd3tw67nnyhowcwgdzipb5e):
|
||||
dependencies:
|
||||
'@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@expo/schema-utils': 0.1.7
|
||||
'@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0)
|
||||
'@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/native': 7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
client-only: 0.0.1
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))
|
||||
expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))
|
||||
expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
expo-server: 1.0.4
|
||||
fast-deep-equal: 3.1.3
|
||||
invariant: 2.2.4
|
||||
|
|
@ -28348,10 +28536,10 @@ snapshots:
|
|||
query-string: 7.1.3
|
||||
react: 19.1.0
|
||||
react-fast-compare: 3.2.2
|
||||
react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
|
||||
react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
|
||||
react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-safe-area-context: 5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
semver: 7.6.3
|
||||
server-only: 0.0.1
|
||||
sf-symbols-typescript: 2.1.0
|
||||
|
|
@ -28359,13 +28547,13 @@ snapshots:
|
|||
use-latest-callback: 0.2.6(react@19.1.0)
|
||||
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
'@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
|
||||
react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0))
|
||||
react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12))
|
||||
transitivePeerDependencies:
|
||||
- '@react-native-masked-view/masked-view'
|
||||
- '@types/react'
|
||||
|
|
@ -30409,15 +30597,15 @@ snapshots:
|
|||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest-cli@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
|
||||
jest-cli@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))
|
||||
'@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
'@jest/test-result': 30.2.0
|
||||
'@jest/types': 30.2.0
|
||||
chalk: 4.1.2
|
||||
exit-x: 0.2.2
|
||||
import-local: 3.2.0
|
||||
jest-config: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
|
||||
jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
jest-util: 30.2.0
|
||||
jest-validate: 30.2.0
|
||||
yargs: 17.7.2
|
||||
|
|
@ -30580,7 +30768,7 @@ snapshots:
|
|||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
jest-config@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
|
||||
jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.28.5
|
||||
'@jest/get-type': 30.1.0
|
||||
|
|
@ -30607,8 +30795,9 @@ snapshots:
|
|||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.25
|
||||
esbuild-register: 3.6.0(esbuild@0.27.0)
|
||||
'@types/node': 22.19.1
|
||||
esbuild-register: 3.6.0(esbuild@0.19.12)
|
||||
ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
|
@ -31269,12 +31458,12 @@ snapshots:
|
|||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
|
||||
jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))
|
||||
'@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
'@jest/types': 30.2.0
|
||||
import-local: 3.2.0
|
||||
jest-cli: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
|
||||
jest-cli: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
|
|
@ -34897,6 +35086,16 @@ snapshots:
|
|||
webpack: 5.100.2(esbuild@0.27.0)
|
||||
webpack-sources: 3.3.3
|
||||
|
||||
react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12)):
|
||||
dependencies:
|
||||
acorn-loose: 8.5.2
|
||||
neo-async: 2.6.2
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
webpack: 5.97.1(esbuild@0.19.12)
|
||||
webpack-sources: 3.3.3
|
||||
optional: true
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@18.3.27)(react@18.3.1):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
|
|
@ -37001,6 +37200,23 @@ snapshots:
|
|||
tsx: 4.20.6
|
||||
yaml: 2.8.1
|
||||
|
||||
vite@6.4.1(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.53.3
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.1
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.30.2
|
||||
terser: 5.44.1
|
||||
tsx: 4.20.6
|
||||
yaml: 2.8.1
|
||||
|
||||
vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -37060,6 +37276,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
||||
vitefu@1.1.1(vite@6.4.1(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
||||
vitefu@1.1.1(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
|
||||
|
|
@ -37145,7 +37365,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/debug': 4.1.12
|
||||
'@types/node': 24.10.1
|
||||
'@vitest/browser': 3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)
|
||||
'@vitest/browser': 3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))(vitest@3.2.4)
|
||||
'@vitest/ui': 3.2.4(vitest@3.2.4)
|
||||
jsdom: 27.2.0
|
||||
transitivePeerDependencies:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue