From 990ade352f31dd347956da6a4455603b7759c97d Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 1 Apr 2026 20:28:00 +0200 Subject: [PATCH] feat(manacore): migrate storage, cards, playground, guides to unified app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 continued — 4 more modules migrated (total: 21/25): - Storage: file browser with folders, favorites, search, trash (7 routes) - Cards: deck/card management with study progress (6 routes) - Playground: LLM chat interface with model selector (stateless) - Guides: guide listing with category filters (static content) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/lib/modules/cards/collections.ts | 59 ++++ .../cards/components/CreateDeckModal.svelte | 127 ++++++++ .../modules/cards/components/DeckCard.svelte | 53 ++++ .../apps/web/src/lib/modules/cards/index.ts | 28 ++ .../apps/web/src/lib/modules/cards/queries.ts | 90 ++++++ .../lib/modules/cards/stores/cards.svelte.ts | 108 +++++++ .../lib/modules/cards/stores/decks.svelte.ts | 81 +++++ .../apps/web/src/lib/modules/cards/types.ts | 77 +++++ .../apps/web/src/lib/modules/guides/index.ts | 75 +++++ .../web/src/lib/modules/playground/index.ts | 23 ++ .../src/lib/modules/storage/collections.ts | 69 +++++ .../apps/web/src/lib/modules/storage/index.ts | 32 ++ .../web/src/lib/modules/storage/queries.ts | 199 +++++++++++++ .../modules/storage/stores/files.svelte.ts | 224 ++++++++++++++ .../lib/modules/storage/stores/tags.svelte.ts | 56 ++++ .../apps/web/src/lib/modules/storage/types.ts | 41 +++ .../web/src/routes/(app)/cards/+layout.svelte | 15 + .../web/src/routes/(app)/cards/+page.svelte | 75 +++++ .../src/routes/(app)/cards/decks/+page.svelte | 71 +++++ .../(app)/cards/decks/[id]/+page.svelte | 278 ++++++++++++++++++ .../routes/(app)/cards/explore/+page.svelte | 26 ++ .../routes/(app)/cards/progress/+page.svelte | 79 +++++ .../web/src/routes/(app)/guides/+page.svelte | 114 +++++++ .../src/routes/(app)/playground/+page.svelte | 188 ++++++++++++ .../src/routes/(app)/storage/+layout.svelte | 19 ++ .../web/src/routes/(app)/storage/+page.svelte | 87 ++++++ .../(app)/storage/favorites/+page.svelte | 153 ++++++++++ .../routes/(app)/storage/files/+page.svelte | 244 +++++++++++++++ .../storage/files/[folderId]/+page.svelte | 239 +++++++++++++++ .../routes/(app)/storage/search/+page.svelte | 198 +++++++++++++ .../routes/(app)/storage/trash/+page.svelte | 156 ++++++++++ 31 files changed, 3284 insertions(+) create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/collections.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/components/DeckCard.svelte create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/queries.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/cards/types.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/guides/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/playground/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/storage/collections.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/storage/index.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/storage/queries.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/storage/stores/tags.svelte.ts create mode 100644 apps/manacore/apps/web/src/lib/modules/storage/types.ts create mode 100644 apps/manacore/apps/web/src/routes/(app)/cards/+layout.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/cards/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/cards/decks/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/cards/explore/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/cards/progress/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/guides/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/playground/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/+layout.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/favorites/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/files/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/files/[folderId]/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/search/+page.svelte create mode 100644 apps/manacore/apps/web/src/routes/(app)/storage/trash/+page.svelte diff --git a/apps/manacore/apps/web/src/lib/modules/cards/collections.ts b/apps/manacore/apps/web/src/lib/modules/cards/collections.ts new file mode 100644 index 000000000..c7cd5daeb --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/collections.ts @@ -0,0 +1,59 @@ +/** + * Cards module — collection accessors and guest seed data. + * + * Uses table names from the unified DB: cardDecks, cards. + */ + +import { db } from '$lib/data/database'; +import type { LocalDeck, LocalCard } from './types'; + +// ─── Collection Accessors ────────────────────────────────── + +export const cardDeckTable = db.table('cardDecks'); +export const cardTable = db.table('cards'); + +// ─── Guest Seed ──────────────────────────────────────────── + +const ONBOARDING_DECK_ID = 'onboarding-deck'; + +export const CARDS_GUEST_SEED = { + cardDecks: [ + { + id: ONBOARDING_DECK_ID, + name: 'Erste Schritte', + description: 'Lerne Cards kennen mit diesen Beispiel-Karteikarten.', + color: '#6366f1', + cardCount: 3, + isPublic: false, + }, + ], + cards: [ + { + id: 'card-1', + deckId: ONBOARDING_DECK_ID, + front: 'Was ist Cards?', + back: 'Cards ist eine Karteikarten-App zum effizienten Lernen mit Spaced Repetition.', + difficulty: 1, + reviewCount: 0, + order: 0, + }, + { + id: 'card-2', + deckId: ONBOARDING_DECK_ID, + front: 'Wie funktioniert Spaced Repetition?', + back: 'Karten, die du gut kennst, werden seltener gezeigt. Schwierige Karten erscheinen haufiger, bis du sie beherrschst.', + difficulty: 2, + reviewCount: 0, + order: 1, + }, + { + id: 'card-3', + deckId: ONBOARDING_DECK_ID, + front: 'Wie erstelle ich ein neues Deck?', + back: 'Klicke auf den + Button auf der Decks-Seite, um ein neues Deck mit eigenen Karteikarten zu erstellen.', + difficulty: 1, + reviewCount: 0, + order: 2, + }, + ], +}; diff --git a/apps/manacore/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte b/apps/manacore/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte new file mode 100644 index 000000000..4d4bdbb1b --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte @@ -0,0 +1,127 @@ + + +{#if open} + + +
+
e.stopPropagation()} + > +

Neues Deck erstellen

+ +
{ + e.preventDefault(); + handleSubmit(); + }} + class="space-y-4" + > +
+ + +
+ +
+ + +
+ +
+ + +
+ + {#if deckStore.error} +
+ {deckStore.error} +
+ {/if} + +
+ + +
+
+
+
+{/if} diff --git a/apps/manacore/apps/web/src/lib/modules/cards/components/DeckCard.svelte b/apps/manacore/apps/web/src/lib/modules/cards/components/DeckCard.svelte new file mode 100644 index 000000000..2c7f6c875 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/components/DeckCard.svelte @@ -0,0 +1,53 @@ + + + diff --git a/apps/manacore/apps/web/src/lib/modules/cards/index.ts b/apps/manacore/apps/web/src/lib/modules/cards/index.ts new file mode 100644 index 000000000..f063df7aa --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/index.ts @@ -0,0 +1,28 @@ +/** + * Cards module — barrel exports. + */ + +export { deckStore } from './stores/decks.svelte'; +export { cardStore } from './stores/cards.svelte'; +export { + useAllDecks, + useDeck, + useCardsByDeck, + toDeck, + toCard, + getDeckById, + getPublicDecks, + getCardCountForDeck, + getDueCards, +} from './queries'; +export { cardDeckTable, cardTable, CARDS_GUEST_SEED } from './collections'; +export type { + LocalDeck, + LocalCard, + Deck, + Card, + CreateDeckInput, + UpdateDeckInput, + CreateCardInput, + UpdateCardInput, +} from './types'; diff --git a/apps/manacore/apps/web/src/lib/modules/cards/queries.ts b/apps/manacore/apps/web/src/lib/modules/cards/queries.ts new file mode 100644 index 000000000..c53c10934 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/queries.ts @@ -0,0 +1,90 @@ +/** + * Reactive queries & pure helpers for Cards — uses Dexie liveQuery on the unified DB. + * + * Uses table names: cardDecks, cards. + */ + +import { liveQuery } from 'dexie'; +import { db } from '$lib/data/database'; +import type { LocalDeck, LocalCard, Deck, Card } from './types'; + +// ─── Type Converters ─────────────────────────────────────── + +export function toDeck(local: LocalDeck): Deck { + return { + id: local.id, + userId: 'local', + title: local.name, + description: local.description ?? undefined, + color: local.color, + isPublic: local.isPublic, + tags: [], + cardCount: local.cardCount, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +export function toCard(local: LocalCard): Card { + return { + id: local.id, + deckId: local.deckId, + front: local.front, + back: local.back, + difficulty: local.difficulty, + nextReview: local.nextReview ?? undefined, + reviewCount: local.reviewCount, + order: local.order, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +// ─── Live Queries ────────────────────────────────────────── + +/** All decks, auto-updates on any change. */ +export function useAllDecks() { + return liveQuery(async () => { + const locals = await db.table('cardDecks').toArray(); + return locals.filter((d) => !d.deletedAt).map(toDeck); + }); +} + +/** Single deck by ID. Auto-updates on any change. */ +export function useDeck(deckId: string) { + return liveQuery(async () => { + const local = await db.table('cardDecks').get(deckId); + return local && !local.deletedAt ? toDeck(local) : null; + }); +} + +/** All cards for a specific deck, sorted by order. Auto-updates on any change. */ +export function useCardsByDeck(deckId: string) { + return liveQuery(async () => { + const locals = await db + .table('cards') + .where('deckId') + .equals(deckId) + .sortBy('order'); + return locals.filter((c) => !c.deletedAt).map(toCard); + }); +} + +// ─── Pure Helper Functions ───────────────────────────────── + +export function getDeckById(decks: Deck[], id: string): Deck | undefined { + return decks.find((d) => d.id === id); +} + +export function getPublicDecks(decks: Deck[]): Deck[] { + return decks.filter((d) => d.isPublic); +} + +export function getCardCountForDeck(cards: Card[], deckId: string): number { + return cards.filter((c) => c.deckId === deckId).length; +} + +export function getDueCards(cards: Card[]): Card[] { + const now = new Date().toISOString(); + return cards.filter((c) => c.nextReview && c.nextReview <= now); +} diff --git a/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts b/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts new file mode 100644 index 000000000..c7d8c314a --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts @@ -0,0 +1,108 @@ +/** + * Card Store — Mutations Only + * + * Reads come from liveQuery hooks in queries.ts. + * This store only handles writes to IndexedDB via the unified database. + */ + +import { cardTable, cardDeckTable } from '../collections'; +import { toCard } from '../queries'; +import type { LocalCard, Card, CreateCardInput, UpdateCardInput } from '../types'; + +let error = $state(null); + +export const cardStore = { + get error() { + return error; + }, + + async createCard(input: CreateCardInput, currentCardCount: number = 0): Promise { + error = null; + try { + const newLocal: LocalCard = { + id: crypto.randomUUID(), + deckId: input.deckId, + front: input.front, + back: input.back, + difficulty: 1, + reviewCount: 0, + order: currentCardCount, + }; + + await cardTable.add(newLocal); + + // Update deck card count + const deck = await cardDeckTable.get(input.deckId); + if (deck) { + await cardDeckTable.update(input.deckId, { + cardCount: (deck.cardCount || 0) + 1, + updatedAt: new Date().toISOString(), + }); + } + + return toCard(newLocal); + } catch (err: any) { + error = err.message || 'Failed to create card'; + console.error('Create card error:', err); + return null; + } + }, + + async updateCard(id: string, updates: UpdateCardInput) { + error = null; + try { + const localUpdates: Partial = {}; + if (updates.front !== undefined) localUpdates.front = updates.front; + if (updates.back !== undefined) localUpdates.back = updates.back; + if (updates.difficulty !== undefined) localUpdates.difficulty = updates.difficulty; + if (updates.order !== undefined) localUpdates.order = updates.order; + + await cardTable.update(id, { + ...localUpdates, + updatedAt: new Date().toISOString(), + }); + } catch (err: any) { + error = err.message || 'Failed to update card'; + console.error('Update card error:', err); + } + }, + + async deleteCard(id: string, deckId?: string) { + error = null; + try { + const now = new Date().toISOString(); + await cardTable.update(id, { deletedAt: now, updatedAt: now }); + + // Update deck card count + if (deckId) { + const deck = await cardDeckTable.get(deckId); + if (deck) { + await cardDeckTable.update(deckId, { + cardCount: Math.max(0, (deck.cardCount || 0) - 1), + updatedAt: now, + }); + } + } + } catch (err: any) { + error = err.message || 'Failed to delete card'; + console.error('Delete card error:', err); + } + }, + + async reorderCards(cardIds: string[]) { + error = null; + try { + const now = new Date().toISOString(); + for (let i = 0; i < cardIds.length; i++) { + await cardTable.update(cardIds[i], { order: i, updatedAt: now }); + } + } catch (err: any) { + error = err.message || 'Failed to reorder cards'; + console.error('Reorder cards error:', err); + } + }, + + clearError() { + error = null; + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts b/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts new file mode 100644 index 000000000..acd95136c --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts @@ -0,0 +1,81 @@ +/** + * Deck Store — Mutations Only + * + * Reads come from liveQuery hooks in queries.ts. + * This store only handles writes to IndexedDB via the unified database. + */ + +import { cardDeckTable, cardTable } from '../collections'; +import { toDeck } from '../queries'; +import type { LocalDeck } from '../types'; +import type { Deck, CreateDeckInput, UpdateDeckInput } from '../types'; + +let error = $state(null); + +export const deckStore = { + get error() { + return error; + }, + + async createDeck(input: CreateDeckInput): Promise { + error = null; + try { + const newLocal: LocalDeck = { + id: crypto.randomUUID(), + name: input.title, + description: input.description ?? null, + color: '#6366f1', + cardCount: 0, + isPublic: input.isPublic ?? false, + }; + + await cardDeckTable.add(newLocal); + return toDeck(newLocal); + } catch (err: any) { + error = err.message || 'Failed to create deck'; + console.error('Create deck error:', err); + return null; + } + }, + + async updateDeck(id: string, updates: UpdateDeckInput) { + error = null; + try { + const localUpdates: Partial = {}; + if (updates.title !== undefined) localUpdates.name = updates.title; + if (updates.description !== undefined) localUpdates.description = updates.description; + if (updates.isPublic !== undefined) localUpdates.isPublic = updates.isPublic; + + await cardDeckTable.update(id, { + ...localUpdates, + updatedAt: new Date().toISOString(), + }); + } catch (err: any) { + error = err.message || 'Failed to update deck'; + console.error('Update deck error:', err); + } + }, + + async deleteDeck(id: string) { + error = null; + try { + const now = new Date().toISOString(); + + // Soft-delete all cards belonging to this deck + const cards = await cardTable.where('deckId').equals(id).toArray(); + for (const card of cards) { + await cardTable.update(card.id, { deletedAt: now, updatedAt: now }); + } + + // Soft-delete the deck + await cardDeckTable.update(id, { deletedAt: now, updatedAt: now }); + } catch (err: any) { + error = err.message || 'Failed to delete deck'; + console.error('Delete deck error:', err); + } + }, + + clearError() { + error = null; + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/cards/types.ts b/apps/manacore/apps/web/src/lib/modules/cards/types.ts new file mode 100644 index 000000000..442410ac5 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/cards/types.ts @@ -0,0 +1,77 @@ +/** + * Cards module types for the unified app. + */ + +import type { BaseRecord } from '@manacore/local-store'; + +export interface LocalDeck extends BaseRecord { + name: string; + description?: string | null; + color: string; + cardCount: number; + lastStudied?: string | null; + isPublic: boolean; +} + +export interface LocalCard extends BaseRecord { + deckId: string; + front: string; + back: string; + difficulty: number; // 1-5 + nextReview?: string | null; + reviewCount: number; + order: number; +} + +// ─── View Types (inline to avoid @cards/shared dependency) ── + +export interface Deck { + id: string; + userId: string; + title: string; + description?: string; + color: string; + isPublic: boolean; + tags: string[]; + cardCount: number; + createdAt: string; + updatedAt: string; +} + +export interface Card { + id: string; + deckId: string; + front: string; + back: string; + difficulty: number; + nextReview?: string; + reviewCount: number; + order: number; + createdAt: string; + updatedAt: string; +} + +export interface CreateDeckInput { + title: string; + description?: string; + isPublic?: boolean; +} + +export interface UpdateDeckInput { + title?: string; + description?: string; + isPublic?: boolean; +} + +export interface CreateCardInput { + deckId: string; + front: string; + back: string; +} + +export interface UpdateCardInput { + front?: string; + back?: string; + difficulty?: number; + order?: number; +} diff --git a/apps/manacore/apps/web/src/lib/modules/guides/index.ts b/apps/manacore/apps/web/src/lib/modules/guides/index.ts new file mode 100644 index 000000000..80434ae6a --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/guides/index.ts @@ -0,0 +1,75 @@ +/** + * Guides module — barrel exports. + * + * Interactive guides and tutorials for the ManaCore ecosystem. + * No local-first collections needed yet (static content). + */ + +export interface Guide { + id: string; + title: string; + description: string; + category: GuideCategory; + difficulty: 'beginner' | 'intermediate' | 'advanced'; + estimatedMinutes: number; +} + +export type GuideCategory = 'getting-started' | 'productivity' | 'advanced' | 'integrations'; + +export const GUIDE_CATEGORIES: Record = { + 'getting-started': { label: 'Erste Schritte', color: 'bg-emerald-500' }, + productivity: { label: 'Produktivität', color: 'bg-blue-500' }, + advanced: { label: 'Fortgeschritten', color: 'bg-violet-500' }, + integrations: { label: 'Integrationen', color: 'bg-amber-500' }, +}; + +export const GUIDES: Guide[] = [ + { + id: 'welcome', + title: 'Willkommen bei ManaCore', + description: 'Ein Überblick über das ManaCore-Ökosystem und seine Apps.', + category: 'getting-started', + difficulty: 'beginner', + estimatedMinutes: 5, + }, + { + id: 'local-first', + title: 'Offline-First verstehen', + description: 'Wie ManaCore lokal arbeitet und im Hintergrund synchronisiert.', + category: 'getting-started', + difficulty: 'beginner', + estimatedMinutes: 8, + }, + { + id: 'keyboard-shortcuts', + title: 'Tastaturkürzel', + description: 'Navigiere schneller mit Tastaturkürzeln durch alle Apps.', + category: 'productivity', + difficulty: 'beginner', + estimatedMinutes: 5, + }, + { + id: 'todo-workflows', + title: 'Todo-Workflows', + description: 'Projekte, Labels und Fokus-Modus effektiv nutzen.', + category: 'productivity', + difficulty: 'intermediate', + estimatedMinutes: 10, + }, + { + id: 'ai-features', + title: 'KI-Funktionen nutzen', + description: 'Chat, Playground und KI-gestützte Features in ManaCore.', + category: 'advanced', + difficulty: 'intermediate', + estimatedMinutes: 12, + }, + { + id: 'sync-setup', + title: 'Sync einrichten', + description: 'Geräteübergreifende Synchronisation konfigurieren.', + category: 'integrations', + difficulty: 'intermediate', + estimatedMinutes: 8, + }, +]; diff --git a/apps/manacore/apps/web/src/lib/modules/playground/index.ts b/apps/manacore/apps/web/src/lib/modules/playground/index.ts new file mode 100644 index 000000000..b929b37a9 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/playground/index.ts @@ -0,0 +1,23 @@ +/** + * Playground module — barrel exports. + * + * Stateless LLM playground for testing prompts against different models. + * No local-first collections needed (no persistent data model). + */ + +export const PLAYGROUND_MODELS = [ + { id: 'claude-sonnet', label: 'Claude Sonnet', provider: 'Anthropic' }, + { id: 'claude-haiku', label: 'Claude Haiku', provider: 'Anthropic' }, + { id: 'gpt-4o', label: 'GPT-4o', provider: 'OpenAI' }, + { id: 'gpt-4o-mini', label: 'GPT-4o Mini', provider: 'OpenAI' }, + { id: 'gemini-pro', label: 'Gemini Pro', provider: 'Google' }, + { id: 'gemini-flash', label: 'Gemini Flash', provider: 'Google' }, +] as const; + +export type PlaygroundModel = (typeof PLAYGROUND_MODELS)[number]['id']; + +export interface PlaygroundMessage { + role: 'user' | 'assistant'; + content: string; + timestamp: number; +} diff --git a/apps/manacore/apps/web/src/lib/modules/storage/collections.ts b/apps/manacore/apps/web/src/lib/modules/storage/collections.ts new file mode 100644 index 000000000..7aec05024 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/storage/collections.ts @@ -0,0 +1,69 @@ +/** + * Storage module — collection accessors and guest seed data. + * + * Uses table names from the unified DB: files, storageFolders, storageTags, fileTags. + */ + +import { db } from '$lib/data/database'; +import type { LocalFile, LocalFolder, LocalTag, LocalFileTag } from './types'; + +// ─── Collection Accessors ────────────────────────────────── + +export const fileTable = db.table('files'); +export const storageFolderTable = db.table('storageFolders'); +export const storageTagTable = db.table('storageTags'); +export const fileTagTable = db.table('fileTags'); + +// ─── Guest Seed ──────────────────────────────────────────── + +export const STORAGE_GUEST_SEED = { + storageFolders: [ + { + id: 'folder-documents', + name: 'Dokumente', + description: 'Wichtige Dokumente', + color: '#3b82f6', + path: '/folder-documents', + depth: 0, + isFavorite: false, + isDeleted: false, + }, + { + id: 'folder-photos', + name: 'Fotos', + description: 'Fotosammlung', + color: '#22c55e', + path: '/folder-photos', + depth: 0, + isFavorite: true, + isDeleted: false, + }, + { + id: 'folder-music', + name: 'Musik', + description: 'Audio-Dateien', + color: '#a855f7', + path: '/folder-music', + depth: 0, + isFavorite: false, + isDeleted: false, + }, + ], + storageTags: [ + { + id: 'tag-important', + name: 'Wichtig', + color: '#ef4444', + }, + { + id: 'tag-work', + name: 'Arbeit', + color: '#3b82f6', + }, + { + id: 'tag-personal', + name: 'Privat', + color: '#22c55e', + }, + ], +}; diff --git a/apps/manacore/apps/web/src/lib/modules/storage/index.ts b/apps/manacore/apps/web/src/lib/modules/storage/index.ts new file mode 100644 index 000000000..f05799703 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/storage/index.ts @@ -0,0 +1,32 @@ +/** + * Storage module — barrel exports. + */ + +export { filesStore } from './stores/files.svelte'; +export { storageTagStore } from './stores/tags.svelte'; +export { + useAllFiles, + useAllFolders, + useAllStorageTags, + toFile, + toFolder, + toTag, + getFilesInFolder, + getFoldersInFolder, + getFavoriteFiles, + getFavoriteFolders, + findFolderById, + getDeletedFiles, + getDeletedFolders, + searchItems, + formatFileSize, +} from './queries'; +export type { StorageFile, StorageFolder, StorageTag } from './queries'; +export { + fileTable, + storageFolderTable, + storageTagTable, + fileTagTable, + STORAGE_GUEST_SEED, +} from './collections'; +export type { LocalFile, LocalFolder, LocalTag, LocalFileTag } from './types'; diff --git a/apps/manacore/apps/web/src/lib/modules/storage/queries.ts b/apps/manacore/apps/web/src/lib/modules/storage/queries.ts new file mode 100644 index 000000000..279fad94b --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/storage/queries.ts @@ -0,0 +1,199 @@ +/** + * Reactive queries & pure helpers for Storage — uses Dexie liveQuery on the unified DB. + * + * Uses table names: files, storageFolders, storageTags, fileTags. + */ + +import { liveQuery } from 'dexie'; +import { db } from '$lib/data/database'; +import type { LocalFile, LocalFolder, LocalTag, LocalFileTag } from './types'; + +// ─── Shared Types (inline to avoid @storage/shared dependency) ─── + +export interface StorageFile { + id: string; + userId: string; + name: string; + originalName: string; + mimeType: string; + size: number; + storagePath: string; + storageKey: string; + parentFolderId: string | null; + currentVersion: number; + isFavorite: boolean; + isDeleted: boolean; + deletedAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface StorageFolder { + id: string; + userId: string; + name: string; + description: string | null; + color: string | null; + parentFolderId: string | null; + path: string; + depth: number; + isFavorite: boolean; + isDeleted: boolean; + deletedAt: string | null; + createdAt: string; + updatedAt: string; +} + +export interface StorageTag { + id: string; + userId: string; + name: string; + color: string | null; + createdAt: string; +} + +// ─── Type Converters ─────────────────────────────────────── + +export function toFile(local: LocalFile): StorageFile { + return { + id: local.id, + userId: 'local', + name: local.name, + originalName: local.originalName, + mimeType: local.mimeType, + size: local.size, + storagePath: local.storagePath, + storageKey: local.storageKey, + parentFolderId: local.parentFolderId ?? null, + currentVersion: local.currentVersion, + isFavorite: local.isFavorite, + isDeleted: local.isDeleted, + deletedAt: null, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +export function toFolder(local: LocalFolder): StorageFolder { + return { + id: local.id, + userId: 'local', + name: local.name, + description: local.description ?? null, + color: local.color ?? null, + parentFolderId: local.parentFolderId ?? null, + path: local.path, + depth: local.depth, + isFavorite: local.isFavorite, + isDeleted: local.isDeleted, + deletedAt: null, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +export function toTag(local: LocalTag): StorageTag { + return { + id: local.id, + userId: 'local', + name: local.name, + color: local.color ?? null, + createdAt: local.createdAt ?? new Date().toISOString(), + }; +} + +// ─── Live Queries ────────────────────────────────────────── + +/** All non-deleted files, sorted by name. Auto-updates on any change. */ +export function useAllFiles() { + return liveQuery(async () => { + const locals = await db.table('files').toArray(); + return locals + .filter((f) => !f.isDeleted && !f.deletedAt) + .map(toFile) + .sort((a, b) => a.name.localeCompare(b.name)); + }); +} + +/** All non-deleted folders, sorted by name. Auto-updates on any change. */ +export function useAllFolders() { + return liveQuery(async () => { + const locals = await db.table('storageFolders').toArray(); + return locals + .filter((f) => !f.isDeleted && !f.deletedAt) + .map(toFolder) + .sort((a, b) => a.name.localeCompare(b.name)); + }); +} + +/** All tags, sorted by name. Auto-updates on any change. */ +export function useAllStorageTags() { + return liveQuery(async () => { + const locals = await db.table('storageTags').toArray(); + return locals + .filter((t) => !t.deletedAt) + .map(toTag) + .sort((a, b) => a.name.localeCompare(b.name)); + }); +} + +// ─── Pure Helper Functions (for $derived) ───────────────── + +/** Get files in a specific folder (null = root). */ +export function getFilesInFolder(files: StorageFile[], folderId: string | null): StorageFile[] { + return files.filter((f) => (f.parentFolderId ?? null) === folderId); +} + +/** Get subfolders of a specific folder (null = root). */ +export function getFoldersInFolder( + folders: StorageFolder[], + parentFolderId: string | null +): StorageFolder[] { + return folders.filter((f) => (f.parentFolderId ?? null) === parentFolderId); +} + +/** Get favorite files. */ +export function getFavoriteFiles(files: StorageFile[]): StorageFile[] { + return files.filter((f) => f.isFavorite); +} + +/** Get favorite folders. */ +export function getFavoriteFolders(folders: StorageFolder[]): StorageFolder[] { + return folders.filter((f) => f.isFavorite); +} + +/** Find a folder by ID. */ +export function findFolderById(folders: StorageFolder[], id: string): StorageFolder | undefined { + return folders.find((f) => f.id === id); +} + +/** Get deleted files. */ +export function getDeletedFiles(files: StorageFile[]): StorageFile[] { + return files.filter((f) => f.isDeleted); +} + +/** Get deleted folders. */ +export function getDeletedFolders(folders: StorageFolder[]): StorageFolder[] { + return folders.filter((f) => f.isDeleted); +} + +/** Search files and folders by name query. */ +export function searchItems( + files: StorageFile[], + folders: StorageFolder[], + query: string +): { files: StorageFile[]; folders: StorageFolder[] } { + const q = query.toLowerCase(); + return { + files: files.filter((f) => f.name.toLowerCase().includes(q)), + folders: folders.filter((f) => f.name.toLowerCase().includes(q)), + }; +} + +/** Format file size for display. */ +export function formatFileSize(bytes: number): string { + if (bytes === 0) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${(bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`; +} diff --git a/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts b/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts new file mode 100644 index 000000000..063b1b946 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts @@ -0,0 +1,224 @@ +/** + * Files Store — Mutation-Only (Local-First reads via queries.ts) + * + * Reads are handled by liveQuery hooks in queries.ts. + * This store handles writes, selection state, and view mode. + * Server-side operations (upload, download, share) are not available in the unified app. + */ + +import { fileTable, storageFolderTable } from '../collections'; +import { toFile, toFolder } from '../queries'; +import type { StorageFile, StorageFolder } from '../queries'; +import type { LocalFile, LocalFolder } from '../types'; + +let viewMode = $state<'grid' | 'list'>('grid'); +let selectedFileIds = $state>(new Set()); +let selectedFolderIds = $state>(new Set()); +let currentFolderId = $state(null); + +export const filesStore = { + get viewMode() { + return viewMode; + }, + get selectedFileIds() { + return selectedFileIds; + }, + get selectedFolderIds() { + return selectedFolderIds; + }, + get selectionCount() { + return selectedFileIds.size + selectedFolderIds.size; + }, + get currentFolderId() { + return currentFolderId; + }, + + toggleFileSelection(id: string) { + const next = new Set(selectedFileIds); + if (next.has(id)) next.delete(id); + else next.add(id); + selectedFileIds = next; + }, + + toggleFolderSelection(id: string) { + const next = new Set(selectedFolderIds); + if (next.has(id)) next.delete(id); + else next.add(id); + selectedFolderIds = next; + }, + + selectAllFromLists(files: StorageFile[], folders: StorageFolder[]) { + selectedFileIds = new Set(files.map((f) => f.id)); + selectedFolderIds = new Set(folders.map((f) => f.id)); + }, + + clearSelection() { + selectedFileIds = new Set(); + selectedFolderIds = new Set(); + }, + + setViewMode(mode: 'grid' | 'list') { + viewMode = mode; + if (typeof localStorage !== 'undefined') { + localStorage.setItem('storage-view-mode', mode); + } + }, + + initViewMode() { + if (typeof localStorage !== 'undefined') { + const saved = localStorage.getItem('storage-view-mode'); + if (saved === 'grid' || saved === 'list') { + viewMode = saved; + } + } + }, + + setCurrentFolder(folderId: string | null) { + currentFolderId = folderId; + selectedFileIds = new Set(); + selectedFolderIds = new Set(); + }, + + // ─── Local-First write operations ──────────────────────── + + async createFolder(name: string, color?: string) { + const now = new Date().toISOString(); + const parentId = currentFolderId; + + // Build path + let path = `/${crypto.randomUUID().slice(0, 8)}`; + let depth = 0; + if (parentId) { + const parent = await storageFolderTable.get(parentId); + if (parent) { + path = `${parent.path}/${name.toLowerCase().replace(/\s+/g, '-')}`; + depth = parent.depth + 1; + } + } + + const newFolder: LocalFolder = { + id: crypto.randomUUID(), + name, + description: null, + color: color ?? null, + parentFolderId: parentId, + path, + depth, + isFavorite: false, + isDeleted: false, + }; + + await storageFolderTable.add(newFolder); + return toFolder(newFolder); + }, + + async renameFile(id: string, name: string) { + await fileTable.update(id, { + name, + updatedAt: new Date().toISOString(), + }); + }, + + async renameFolder(id: string, name: string) { + await storageFolderTable.update(id, { + name, + updatedAt: new Date().toISOString(), + }); + }, + + async toggleFileFavorite(id: string) { + const file = await fileTable.get(id); + if (file) { + const newFav = !file.isFavorite; + await fileTable.update(id, { + isFavorite: newFav, + updatedAt: new Date().toISOString(), + }); + return newFav; + } + return false; + }, + + async toggleFolderFavorite(id: string) { + const folder = await storageFolderTable.get(id); + if (folder) { + const newFav = !folder.isFavorite; + await storageFolderTable.update(id, { + isFavorite: newFav, + updatedAt: new Date().toISOString(), + }); + return newFav; + } + return false; + }, + + async deleteFile(id: string) { + await fileTable.update(id, { + isDeleted: true, + updatedAt: new Date().toISOString(), + }); + }, + + async deleteFolder(id: string) { + await storageFolderTable.update(id, { + isDeleted: true, + updatedAt: new Date().toISOString(), + }); + }, + + async restoreFile(id: string) { + await fileTable.update(id, { + isDeleted: false, + updatedAt: new Date().toISOString(), + }); + }, + + async restoreFolder(id: string) { + await storageFolderTable.update(id, { + isDeleted: false, + updatedAt: new Date().toISOString(), + }); + }, + + async permanentDeleteFile(id: string) { + await fileTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }, + + async permanentDeleteFolder(id: string) { + await storageFolderTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }, + + async moveFile(id: string, targetFolderId: string) { + await fileTable.update(id, { + parentFolderId: targetFolderId, + updatedAt: new Date().toISOString(), + }); + }, + + async moveFolder(id: string, targetFolderId: string) { + await storageFolderTable.update(id, { + parentFolderId: targetFolderId, + updatedAt: new Date().toISOString(), + }); + }, + + async deleteSelected() { + const now = new Date().toISOString(); + for (const id of selectedFileIds) { + await fileTable.update(id, { isDeleted: true, updatedAt: now }); + } + for (const id of selectedFolderIds) { + await storageFolderTable.update(id, { isDeleted: true, updatedAt: now }); + } + const count = selectedFileIds.size + selectedFolderIds.size; + selectedFileIds = new Set(); + selectedFolderIds = new Set(); + return count; + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/storage/stores/tags.svelte.ts b/apps/manacore/apps/web/src/lib/modules/storage/stores/tags.svelte.ts new file mode 100644 index 000000000..17a684183 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/storage/stores/tags.svelte.ts @@ -0,0 +1,56 @@ +/** + * Storage Tag Store — Mutations Only + * + * Reads come from liveQuery hooks in queries.ts. + * This store only handles writes to IndexedDB via the unified database. + */ + +import { storageTagTable, fileTagTable } from '../collections'; +import type { LocalTag, LocalFileTag } from '../types'; + +export const storageTagStore = { + async create(name: string, color?: string) { + const newTag: LocalTag = { + id: crypto.randomUUID(), + name, + color: color ?? null, + }; + await storageTagTable.add(newTag); + return newTag; + }, + + async update(id: string, data: Partial>) { + await storageTagTable.update(id, { + ...data, + updatedAt: new Date().toISOString(), + }); + }, + + async delete(id: string) { + await storageTagTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }, + + async tagFile(fileId: string, tagId: string) { + const existing = await fileTagTable.where('[fileId+tagId]').equals([fileId, tagId]).first(); + if (existing) return; + + const newFileTag: LocalFileTag = { + id: crypto.randomUUID(), + fileId, + tagId, + }; + await fileTagTable.add(newFileTag); + }, + + async untagFile(fileId: string, tagId: string) { + const existing = await fileTagTable.where('[fileId+tagId]').equals([fileId, tagId]).first(); + if (existing) { + await fileTagTable.update(existing.id, { + deletedAt: new Date().toISOString(), + }); + } + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/storage/types.ts b/apps/manacore/apps/web/src/lib/modules/storage/types.ts new file mode 100644 index 000000000..7d7f644a2 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/storage/types.ts @@ -0,0 +1,41 @@ +/** + * Storage module types for the unified app. + */ + +import type { BaseRecord } from '@manacore/local-store'; + +export interface LocalFile extends BaseRecord { + name: string; + originalName: string; + mimeType: string; + size: number; + storagePath: string; + storageKey: string; + parentFolderId?: string | null; + currentVersion: number; + isFavorite: boolean; + isDeleted: boolean; + checksum?: string | null; + thumbnailPath?: string | null; +} + +export interface LocalFolder extends BaseRecord { + name: string; + description?: string | null; + color?: string | null; + parentFolderId?: string | null; + path: string; + depth: number; + isFavorite: boolean; + isDeleted: boolean; +} + +export interface LocalTag extends BaseRecord { + name: string; + color?: string | null; +} + +export interface LocalFileTag extends BaseRecord { + fileId: string; + tagId: string; +} diff --git a/apps/manacore/apps/web/src/routes/(app)/cards/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/cards/+layout.svelte new file mode 100644 index 000000000..7e0db5dc8 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/cards/+layout.svelte @@ -0,0 +1,15 @@ + + +{@render children()} diff --git a/apps/manacore/apps/web/src/routes/(app)/cards/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/cards/+page.svelte new file mode 100644 index 000000000..bf02bb82e --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/cards/+page.svelte @@ -0,0 +1,75 @@ + + + + Cards - ManaCore + + +
+
+

Cards

+

Karteikarten & Spaced Repetition

+
+ + +
+
+
+ +
+
+
Lerne effizienter
+
+ Karteikarten erstellen, organisieren und mit Spaced Repetition lernen +
+
+
+
+ + +
+ {#each quickLinks as link} + +
+
+ +
+
+
{link.label}
+
{link.description}
+
+
+
+ {/each} +
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/cards/decks/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/cards/decks/+page.svelte new file mode 100644 index 000000000..a815c62aa --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/cards/decks/+page.svelte @@ -0,0 +1,71 @@ + + + + Meine Decks - Cards - ManaCore + + +
+ +
+
+

Meine Decks

+

Organisiere deine Lernmaterialien in Decks

+
+ +
+ + + {#if deckStore.error} +
+

Fehler beim Laden

+

{deckStore.error}

+
+ {:else if (allDecks?.value ?? []).length === 0} + +
+
📚
+

Noch keine Decks

+

+ Erstelle dein erstes Deck, um mit dem Lernen zu beginnen. +

+ +
+ {:else} + +
+ {#each allDecks.value as deck (deck.id)} + handleDeckClick(deck.id)} /> + {/each} +
+ {/if} +
+ + + diff --git a/apps/manacore/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte new file mode 100644 index 000000000..1e4e45ee0 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte @@ -0,0 +1,278 @@ + + + + {deck?.title || 'Deck'} - Cards - ManaCore + + +{#if deck} +
+ + + + +
+
+
+
+

{deck.title}

+
+ {#if deck.description} +

{deck.description}

+ {/if} +
+ +
+ {#if deck.isPublic} + + Offentlich + + {/if} + +
+
+ + +
+
+
{cards.length}
+
Karten gesamt
+
+
+
+ {cards.filter((c) => c.difficulty <= 2).length} +
+
Einfach
+
+
+
+ {cards.filter((c) => c.difficulty >= 4).length} +
+
Schwierig
+
+
+ + +
+ +
+ + + {#if showNewCardForm} +
+

Neue Karte

+
+
+ + +
+
+ + +
+
+ + +
+
+
+ {/if} + + +
+

+ Karten ({cards.length}) +

+ {#if cards.length === 0} +
+
📝
+

Noch keine Karten. Erstelle deine erste Karte!

+ +
+ {:else} +
+ {#each cards as card, i (card.id)} +
+ {i + 1}. +
+
{card.front}
+
{card.back}
+
+
+ + {card.difficulty}/5 + + +
+
+ {/each} +
+ {/if} +
+ + + {#if showDeleteConfirm} + + +
(showDeleteConfirm = false)} + > +
e.stopPropagation()} + > +

Deck loschen?

+

+ Mochtest du "{deck.title}" wirklich loschen? Diese Aktion kann nicht ruckgangig gemacht + werden und loscht auch alle Karten in diesem Deck. +

+
+ + +
+
+
+ {/if} +
+{:else} +
+

Deck nicht gefunden

+ +
+{/if} diff --git a/apps/manacore/apps/web/src/routes/(app)/cards/explore/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/cards/explore/+page.svelte new file mode 100644 index 000000000..1331809bc --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/cards/explore/+page.svelte @@ -0,0 +1,26 @@ + + + + Entdecken - Cards - ManaCore + + +
+
+

Entdecken

+

Offentliche Decks aus der Community entdecken

+
+ +
+
+
+ +
+

Entdecken-Feature

+

Offentliche Decks durchsuchen und entdecken — kommt bald!

+
+
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/cards/progress/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/cards/progress/+page.svelte new file mode 100644 index 000000000..a8b36cfa8 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/cards/progress/+page.svelte @@ -0,0 +1,79 @@ + + + + Fortschritt - Cards - ManaCore + + +
+
+

Fortschritt

+

Verfolge deinen Lernfortschritt

+
+ + +
+
+
{decks.length}
+
Decks
+
+
+
{totalCards}
+
Karten gesamt
+
+
+
0
+
Fallig zur Wiederholung
+
+
+ + +
+

+ + + Decks Ubersicht + +

+ {#if decks.length === 0} +
+
🎯
+

Noch keine Lernsitzungen.

+

Erstelle ein Deck und beginne zu lernen!

+
+ {:else} +
+ {#each decks as deck (deck.id)} +
+
+
+
+
{deck.title}
+
+ {deck.cardCount || 0} Karten +
+
+
+
+
+ {new Date(deck.updatedAt).toLocaleDateString('de-DE', { + day: '2-digit', + month: 'short', + })} +
+
+
+ {/each} +
+ {/if} +
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/guides/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/guides/+page.svelte new file mode 100644 index 000000000..4187b98aa --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/guides/+page.svelte @@ -0,0 +1,114 @@ + + + + Guides - ManaCore + + +
+
+

Guides

+

+ Tutorials & Anleitungen für das ManaCore-Ökosystem +

+
+ + +
+ + {#each categories as [key, cat]} + + {/each} +
+ + + {#if filteredGuides.length === 0} +
+ +

Keine Guides in dieser Kategorie.

+
+ {:else} +
+ {#each filteredGuides as guide} + {@const cat = GUIDE_CATEGORIES[guide.category]} +
+
+ +
+ +
+

{guide.title}

+

{guide.description}

+
+ + + {guide.estimatedMinutes} Min. + + + {difficultyLabel(guide.difficulty)} + +
+
+ + +
+ {/each} +
+ {/if} + + +
+

+ Weitere Guides werden laufend hinzugefügt. Die Inhalte sind aktuell noch Platzhalter. +

+
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/playground/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/playground/+page.svelte new file mode 100644 index 000000000..696d49025 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/playground/+page.svelte @@ -0,0 +1,188 @@ + + + + Playground - ManaCore + + +
+ +
+
+

Playground

+

LLM-Modelle testen & vergleichen

+
+ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ {#if messages.length === 0} +
+
+ +
+

Bereit zum Testen

+

+ Wähle ein Modell, schreibe einen Prompt und teste verschiedene LLMs. Aktuell: {currentModelLabel} +

+
+ {:else} + {#each messages as message} +
+
+ {message.role === 'user' ? 'Du' : currentModelLabel} +
+
{message.content}
+
+ {/each} + + {#if isLoading} +
+
{currentModelLabel}
+
+ + + +
+
+ {/if} + {/if} +
+ + +
+
+ + +
+
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/+layout.svelte new file mode 100644 index 000000000..65f28554f --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/+layout.svelte @@ -0,0 +1,19 @@ + + +{@render children()} diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/+page.svelte new file mode 100644 index 000000000..90247aeb6 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/+page.svelte @@ -0,0 +1,87 @@ + + + + Storage - ManaCore + + +
+
+

Storage

+

Dein Cloud-Speicher

+
+ + +
+
+
+ +
+
+
Cloud-Dateiverwaltung
+
Dateien hochladen, organisieren und teilen
+
+
+
+ + +
+ {#each quickLinks as link} + +
+
+ +
+
+
{link.label}
+
{link.description}
+
+
+
+ {/each} +
+
diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/favorites/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/favorites/+page.svelte new file mode 100644 index 000000000..668b9967c --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/favorites/+page.svelte @@ -0,0 +1,153 @@ + + + + Favoriten - Storage - ManaCore + + +
+
+
+ +

Favoriten

+
+ +
+ + +
+
+ + {#if favoriteFiles.length === 0 && favoriteFolders.length === 0} +
+
+

Keine Favoriten

+

+ Markiere Dateien und Ordner als Favoriten, um sie hier schnell zu finden. +

+
+ {:else if filesStore.viewMode === 'grid'} +
+ {#each favoriteFolders as folder (folder.id)} + + {/each} + {#each favoriteFiles as file (file.id)} +
+
+ {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} +
+ + {file.name} + + {formatFileSize(file.size)} +
+ {/each} +
+ {:else} +
+ {#each favoriteFolders as folder (folder.id)} + + {/each} + {#each favoriteFiles as file (file.id)} +
+ + {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} + +
+
{file.name}
+
{formatFileSize(file.size)}
+
+ + {formatDate(file.updatedAt)} +
+ {/each} +
+ {/if} +
diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/files/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/files/+page.svelte new file mode 100644 index 000000000..a7f5a2ca8 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/files/+page.svelte @@ -0,0 +1,244 @@ + + + + Meine Dateien - Storage - ManaCore + + +
+
+
+

Meine Dateien

+

Alle Dateien und Ordner im Stammverzeichnis

+
+ +
+
+ + +
+ + +
+
+ + + {#if showNewFolderInput} +
+ + e.key === 'Enter' && handleCreateFolder()} + autofocus + /> + + +
+ {/if} + + {#if folders.length === 0 && files.length === 0} + +
+
📂
+

Noch keine Dateien

+

+ Erstelle einen Ordner, um deine Dateien zu organisieren. +

+ +
+ {:else if filesStore.viewMode === 'grid'} + +
+ {#each folders as folder (folder.id)} + + {/each} + {#each files as file (file.id)} +
+
+ {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} +
+ + {file.name} + + {formatFileSize(file.size)} +
+ {/each} +
+ {:else} + +
+ {#each folders as folder (folder.id)} + + {/each} + {#each files as file (file.id)} +
+ + {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} + +
+
{file.name}
+
{formatFileSize(file.size)}
+
+
+ {#if file.isFavorite} + + {/if} + {formatDate(file.updatedAt)} +
+
+ {/each} +
+ {/if} +
diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/files/[folderId]/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/files/[folderId]/+page.svelte new file mode 100644 index 000000000..80590fc56 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/files/[folderId]/+page.svelte @@ -0,0 +1,239 @@ + + + + {currentFolder?.name || 'Ordner'} - Storage - ManaCore + + +
+
+
+ +
+

{currentFolder?.name || 'Ordner'}

+
+
+ +
+
+ + +
+ + +
+
+ + + {#if showNewFolderInput} +
+ + e.key === 'Enter' && handleCreateFolder()} + autofocus + /> + + +
+ {/if} + + {#if folders.length === 0 && files.length === 0} + +
+
📂
+

Leerer Ordner

+

+ Dieser Ordner ist leer. Erstelle Unterordner, um Dateien zu organisieren. +

+ +
+ {:else if filesStore.viewMode === 'grid'} + +
+ {#each folders as folder (folder.id)} + + {/each} + {#each files as file (file.id)} +
+
+ {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} +
+ + {file.name} + + {formatFileSize(file.size)} +
+ {/each} +
+ {:else} + +
+ {#each folders as folder (folder.id)} + + {/each} + {#each files as file (file.id)} +
+ + {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} + +
+
{file.name}
+
{formatFileSize(file.size)}
+
+ {formatDate(file.updatedAt)} +
+ {/each} +
+ {/if} +
diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/search/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/search/+page.svelte new file mode 100644 index 000000000..d69aa075b --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/search/+page.svelte @@ -0,0 +1,198 @@ + + + + Suche - Storage - ManaCore + + +
+
+
+ +

Suche

+
+ +
+ + +
+
+ + +
+ + (searched = true)} + placeholder="Dateien und Ordner durchsuchen..." + class="flex-1 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground" + aria-label="Dateien und Ordner durchsuchen" + autofocus + /> + +
+ + {#if searched && query.trim() && results.files.length === 0 && results.folders.length === 0} +
+
🔍
+

Keine Ergebnisse

+

+ Keine Dateien oder Ordner fur "{query}" gefunden. +

+
+ {:else if searched && query.trim()} +
+ {results.files.length + results.folders.length} Ergebnis(se) fur "{query}" +
+ + {#if filesStore.viewMode === 'grid'} +
+ {#each results.folders as folder (folder.id)} + + {/each} + {#each results.files as file (file.id)} +
+
+ {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} +
+ + {file.name} + + {formatFileSize(file.size)} +
+ {/each} +
+ {:else} +
+ {#each results.folders as folder (folder.id)} + + {/each} + {#each results.files as file (file.id)} +
+ + {#if file.mimeType.startsWith('image/')}📷 + {:else if file.mimeType.startsWith('audio/')}🎵 + {:else if file.mimeType.startsWith('video/')}🎬 + {:else if file.mimeType === 'application/pdf'}📕 + {:else}📄{/if} + +
+
{file.name}
+
{formatFileSize(file.size)}
+
+ {formatDate(file.updatedAt)} +
+ {/each} +
+ {/if} + {:else} +
+
🔍
+

Dateien durchsuchen

+

+ Gib einen Suchbegriff ein, um Dateien und Ordner zu finden. +

+
+ {/if} +
diff --git a/apps/manacore/apps/web/src/routes/(app)/storage/trash/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/storage/trash/+page.svelte new file mode 100644 index 000000000..83a0da45c --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/storage/trash/+page.svelte @@ -0,0 +1,156 @@ + + + + Papierkorb - Storage - ManaCore + + +
+
+
+ +

Papierkorb

+
+ + {#if files.length > 0 || folders.length > 0} + + {/if} +
+ + {#if files.length === 0 && folders.length === 0} +
+
🗑️
+

Papierkorb ist leer

+

Geloschte Dateien und Ordner erscheinen hier.

+
+ {:else} +
+ {#each folders as folder (folder.id)} +
+
+ 📁 +
+
{folder.name}
+
+ Geloscht am {formatDate(folder.updatedAt)} +
+
+
+
+ + +
+
+ {/each} + {#each files as file (file.id)} +
+
+ 📄 +
+
{file.name}
+
+ Geloscht am {formatDate(file.updatedAt)} +
+
+
+
+ + +
+
+ {/each} +
+ {/if} +