mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
refactor(manacore/web): merge entity + app registries into unified AppDescriptor
Replace the dual registry system (app-registry.ts + entities/) with a single AppDescriptor that contains identity, views, and entity fields. - Create $lib/app-registry/ with types.ts, registry.ts, apps.ts, index.ts - Merge all 27 app entries + 3 entity descriptors into one registry - Move ViewProps from nav-stack.ts into app-registry/types.ts - Update all 39 consumer files (ListViews, DetailViews, AppPage, etc.) - Delete old files: entities/, app-registry.ts, nav-stack.ts, entity.ts Single source of truth: one AppDescriptor per module, one registry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9abbf9c70d
commit
c01eccb852
51 changed files with 617 additions and 699 deletions
432
apps/manacore/apps/web/src/lib/app-registry/apps.ts
Normal file
432
apps/manacore/apps/web/src/lib/app-registry/apps.ts
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
/**
|
||||
* Unified App Registrations — All app descriptors in one file.
|
||||
*
|
||||
* Apps with entity capabilities (todo, calendar, contacts) include
|
||||
* collection, paramKey, dragType, etc. for cross-module DnD and linking.
|
||||
* All other apps only declare identity + views.
|
||||
*/
|
||||
|
||||
import { registerApp } from './registry';
|
||||
|
||||
// ── Apps with entity capabilities ───────────────────────────
|
||||
|
||||
registerApp({
|
||||
id: 'todo',
|
||||
name: 'Todo',
|
||||
color: '#8B5CF6',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/todo/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/todo/views/DetailView.svelte') },
|
||||
},
|
||||
collection: 'tasks',
|
||||
paramKey: 'taskId',
|
||||
dragType: 'task',
|
||||
acceptsDropFrom: ['event', 'contact'],
|
||||
transformIncoming: {
|
||||
event: (source) => ({
|
||||
title: source.title as string,
|
||||
dueDate: source.startDate as string,
|
||||
description: source.description as string | undefined,
|
||||
}),
|
||||
contact: (source) => ({
|
||||
title: `Kontaktieren: ${[source.firstName, source.lastName].filter(Boolean).join(' ')}`,
|
||||
}),
|
||||
},
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.title as string) || 'Aufgabe',
|
||||
subtitle: item.dueDate ? new Date(item.dueDate as string).toLocaleDateString('de') : undefined,
|
||||
}),
|
||||
createItem: async (data) => {
|
||||
const { tasksStore } = await import('$lib/modules/todo/stores/tasks.svelte');
|
||||
const task = await tasksStore.createTask(
|
||||
data as { title: string; dueDate?: string; description?: string }
|
||||
);
|
||||
return task.id;
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'calendar',
|
||||
name: 'Kalender',
|
||||
color: '#3B82F6',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/calendar/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/calendar/views/DetailView.svelte') },
|
||||
},
|
||||
collection: 'events',
|
||||
paramKey: 'eventId',
|
||||
dragType: 'event',
|
||||
acceptsDropFrom: ['task', 'contact'],
|
||||
transformIncoming: {
|
||||
task: (source) => {
|
||||
const dueDate = (source.dueDate as string) || new Date().toISOString();
|
||||
const start = new Date(dueDate);
|
||||
const end = new Date(start.getTime() + 60 * 60 * 1000);
|
||||
return {
|
||||
title: source.title as string,
|
||||
startTime: start.toISOString(),
|
||||
endTime: end.toISOString(),
|
||||
description: source.description as string | undefined,
|
||||
};
|
||||
},
|
||||
contact: (source) => {
|
||||
const name = [source.firstName, source.lastName].filter(Boolean).join(' ');
|
||||
const now = new Date();
|
||||
const end = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
return {
|
||||
title: `Treffen mit ${name}`,
|
||||
startTime: now.toISOString(),
|
||||
endTime: end.toISOString(),
|
||||
};
|
||||
},
|
||||
},
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.title as string) || 'Termin',
|
||||
subtitle: item.startDate
|
||||
? new Date(item.startDate as string).toLocaleDateString('de', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: undefined,
|
||||
}),
|
||||
createItem: async (data) => {
|
||||
const { db } = await import('$lib/data/database');
|
||||
const { eventsStore } = await import('$lib/modules/calendar/stores/events.svelte');
|
||||
|
||||
const calendars = await db.table('calendars').toArray();
|
||||
const defaultCal = calendars.find((c: Record<string, unknown>) => !c.deletedAt);
|
||||
const calendarId = (defaultCal?.id as string) ?? 'default';
|
||||
|
||||
const result = await eventsStore.createEvent({
|
||||
calendarId,
|
||||
title: data.title as string,
|
||||
startTime: data.startTime as string,
|
||||
endTime: data.endTime as string,
|
||||
description: (data.description as string) ?? undefined,
|
||||
});
|
||||
|
||||
if (!result.success || !result.data) throw new Error(result.error || 'Failed to create event');
|
||||
return result.data.id;
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'contacts',
|
||||
name: 'Kontakte',
|
||||
color: '#22C55E',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/contacts/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/contacts/views/DetailView.svelte') },
|
||||
},
|
||||
collection: 'contacts',
|
||||
paramKey: 'contactId',
|
||||
dragType: 'contact',
|
||||
getDisplayData: (item) => {
|
||||
const name = [item.firstName, item.lastName].filter(Boolean).join(' ');
|
||||
return {
|
||||
title: name || (item.email as string) || 'Kontakt',
|
||||
subtitle: (item.company as string) ?? undefined,
|
||||
};
|
||||
},
|
||||
// Contacts are drag sources only -- dropping onto contacts doesn't create a new contact
|
||||
});
|
||||
|
||||
// ── Apps without entity capabilities ────────────────────────
|
||||
|
||||
registerApp({
|
||||
id: 'habits',
|
||||
name: 'Habits',
|
||||
color: '#8B5CF6',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/habits/ListView.svelte') },
|
||||
},
|
||||
collection: 'habits',
|
||||
paramKey: 'habitId',
|
||||
dragType: 'habit',
|
||||
acceptsDropFrom: ['task'],
|
||||
transformIncoming: {
|
||||
task: (source) => ({
|
||||
title: source.title as string,
|
||||
emoji: '\u{1F4AA}',
|
||||
color: '#6366f1',
|
||||
}),
|
||||
},
|
||||
getDisplayData: (item) => ({
|
||||
title: `${item.emoji as string} ${item.title as string}`,
|
||||
subtitle: undefined,
|
||||
}),
|
||||
createItem: async (data) => {
|
||||
const { habitsStore } = await import('$lib/modules/habits/stores/habits.svelte');
|
||||
const habit = await habitsStore.createHabit({
|
||||
title: data.title as string,
|
||||
emoji: (data.emoji as string) ?? '\u{2B50}',
|
||||
color: (data.color as string) ?? '#6366f1',
|
||||
});
|
||||
return habit.id;
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'notes',
|
||||
name: 'Notes',
|
||||
color: '#F59E0B',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/notes/ListView.svelte') },
|
||||
},
|
||||
collection: 'notes',
|
||||
paramKey: 'noteId',
|
||||
dragType: 'note',
|
||||
acceptsDropFrom: ['task', 'contact'],
|
||||
transformIncoming: {
|
||||
task: (source) => ({
|
||||
title: source.title as string,
|
||||
content: (source.description as string) ?? '',
|
||||
}),
|
||||
contact: (source) => ({
|
||||
title: `${[source.firstName, source.lastName].filter(Boolean).join(' ')}`,
|
||||
content: `Kontakt: ${[source.firstName, source.lastName].filter(Boolean).join(' ')}`,
|
||||
}),
|
||||
},
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.title as string) || 'Notiz',
|
||||
subtitle: undefined,
|
||||
}),
|
||||
createItem: async (data) => {
|
||||
const { notesStore } = await import('$lib/modules/notes/stores/notes.svelte');
|
||||
const note = await notesStore.createNote({
|
||||
title: data.title as string,
|
||||
content: (data.content as string) ?? '',
|
||||
});
|
||||
return note.id;
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'finance',
|
||||
name: 'Finance',
|
||||
color: '#22C55E',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/finance/ListView.svelte') },
|
||||
},
|
||||
collection: 'transactions',
|
||||
paramKey: 'transactionId',
|
||||
dragType: 'transaction',
|
||||
acceptsDropFrom: [],
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.description as string) || 'Transaktion',
|
||||
subtitle: item.amount ? `${item.type === 'income' ? '+' : '-'}${item.amount}` : undefined,
|
||||
}),
|
||||
createItem: async (data) => {
|
||||
const { financeStore } = await import('$lib/modules/finance/stores/finance.svelte');
|
||||
const tx = await financeStore.addTransaction({
|
||||
type: 'expense',
|
||||
amount: (data.amount as number) ?? 0,
|
||||
description: (data.title as string) ?? (data.description as string) ?? '',
|
||||
});
|
||||
return tx.id;
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'chat',
|
||||
name: 'Chat',
|
||||
color: '#6366F1',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/chat/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'context',
|
||||
name: 'Context',
|
||||
color: '#7C3AED',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/context/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'times',
|
||||
name: 'Times',
|
||||
color: '#F59E0B',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/times/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/times/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'zitare',
|
||||
name: 'Zitare',
|
||||
color: '#EC4899',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/zitare/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/zitare/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'cards',
|
||||
name: 'Cards',
|
||||
color: '#EF4444',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/cards/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/cards/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'picture',
|
||||
name: 'Picture',
|
||||
color: '#8B5CF6',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/picture/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'mukke',
|
||||
name: 'Mukke',
|
||||
color: '#F97316',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/mukke/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/mukke/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'photos',
|
||||
name: 'Photos',
|
||||
color: '#06B6D4',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/photos/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'storage',
|
||||
name: 'Storage',
|
||||
color: '#6B7280',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/storage/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/storage/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'nutriphi',
|
||||
name: 'Nutriphi',
|
||||
color: '#22C55E',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/nutriphi/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'planta',
|
||||
name: 'Planta',
|
||||
color: '#16A34A',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/planta/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/planta/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'presi',
|
||||
name: 'Presi',
|
||||
color: '#A855F7',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/presi/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/presi/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'inventar',
|
||||
name: 'Inventar',
|
||||
color: '#78716C',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/inventar/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/inventar/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'memoro',
|
||||
name: 'Memoro',
|
||||
color: '#F59E0B',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/memoro/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/memoro/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'questions',
|
||||
name: 'Questions',
|
||||
color: '#2563EB',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/questions/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/questions/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'skilltree',
|
||||
name: 'SkillTree',
|
||||
color: '#D946EF',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/skilltree/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/skilltree/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'moodlit',
|
||||
name: 'Moodlit',
|
||||
color: '#F97316',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/moodlit/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'citycorners',
|
||||
name: 'CityCorners',
|
||||
color: '#14B8A6',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/citycorners/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/citycorners/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'uload',
|
||||
name: 'uLoad',
|
||||
color: '#0EA5E9',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/uload/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/uload/views/DetailView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'calc',
|
||||
name: 'Calc',
|
||||
color: '#6B7280',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/calc/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
|
||||
registerApp({
|
||||
id: 'playground',
|
||||
name: 'Playground',
|
||||
color: '#9CA3AF',
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/playground/ListView.svelte') },
|
||||
},
|
||||
});
|
||||
15
apps/manacore/apps/web/src/lib/app-registry/index.ts
Normal file
15
apps/manacore/apps/web/src/lib/app-registry/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Types
|
||||
export type { AppDescriptor, ViewLoader, EntityDisplayData, DropResult, ViewProps } from './types';
|
||||
|
||||
// Registry
|
||||
export {
|
||||
registerApp,
|
||||
getApp,
|
||||
getAppByDragType,
|
||||
canDrop,
|
||||
executeDrop,
|
||||
getAllApps,
|
||||
} from './registry';
|
||||
|
||||
// Register all apps eagerly — descriptors are lightweight with lazy imports
|
||||
import './apps';
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
/**
|
||||
* Entity Registry — Collects module descriptors and orchestrates cross-module drops.
|
||||
* Unified App Registry — Collects app descriptors and orchestrates cross-module drops.
|
||||
*/
|
||||
|
||||
import type { DragType } from '@manacore/shared-ui/dnd';
|
||||
import { linkMutations, buildCachedData } from '@manacore/shared-links';
|
||||
import type { EntityDescriptor, DropResult } from './types';
|
||||
import type { AppDescriptor, DropResult } from './types';
|
||||
|
||||
const entities = new Map<string, EntityDescriptor>();
|
||||
const apps = new Map<string, AppDescriptor>();
|
||||
|
||||
export function registerEntity(descriptor: EntityDescriptor): void {
|
||||
entities.set(descriptor.appId, descriptor);
|
||||
export function registerApp(descriptor: AppDescriptor): void {
|
||||
apps.set(descriptor.id, descriptor);
|
||||
}
|
||||
|
||||
export function getEntity(appId: string): EntityDescriptor | undefined {
|
||||
return entities.get(appId);
|
||||
export function getApp(appId: string): AppDescriptor | undefined {
|
||||
return apps.get(appId);
|
||||
}
|
||||
|
||||
export function getEntityByDragType(type: DragType): EntityDescriptor | undefined {
|
||||
for (const e of entities.values()) {
|
||||
if (e.dragType === type) return e;
|
||||
export function getAppByDragType(type: DragType): AppDescriptor | undefined {
|
||||
for (const a of apps.values()) {
|
||||
if (a.dragType === type) return a;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function canDrop(sourceType: DragType, targetAppId: string): boolean {
|
||||
const target = entities.get(targetAppId);
|
||||
const target = apps.get(targetAppId);
|
||||
if (!target?.acceptsDropFrom?.includes(sourceType)) return false;
|
||||
if (!target.createItem) return false;
|
||||
if (!target.transformIncoming?.[sourceType]) return false;
|
||||
|
|
@ -36,14 +36,18 @@ export async function executeDrop(
|
|||
sourceAppId: string,
|
||||
targetAppId: string
|
||||
): Promise<DropResult> {
|
||||
const source = entities.get(sourceAppId);
|
||||
const target = entities.get(targetAppId);
|
||||
if (!source || !target)
|
||||
throw new Error(`Entity not registered: ${sourceAppId} or ${targetAppId}`);
|
||||
const source = apps.get(sourceAppId);
|
||||
const target = apps.get(targetAppId);
|
||||
if (!source || !target) throw new Error(`App not registered: ${sourceAppId} or ${targetAppId}`);
|
||||
if (!target.createItem) throw new Error(`Target ${targetAppId} has no createItem`);
|
||||
if (!source.dragType) throw new Error(`Source ${sourceAppId} has no dragType`);
|
||||
if (!source.collection) throw new Error(`Source ${sourceAppId} has no collection`);
|
||||
if (!target.collection) throw new Error(`Target ${targetAppId} has no collection`);
|
||||
if (!source.getDisplayData) throw new Error(`Source ${sourceAppId} has no getDisplayData`);
|
||||
if (!target.getDisplayData) throw new Error(`Target ${targetAppId} has no getDisplayData`);
|
||||
|
||||
const transform = target.transformIncoming?.[source.dragType];
|
||||
if (!transform) throw new Error(`No transform for ${source.dragType} → ${targetAppId}`);
|
||||
if (!transform) throw new Error(`No transform for ${source.dragType} -> ${targetAppId}`);
|
||||
|
||||
// 1. Transform source data into target shape
|
||||
const transformedData = transform(sourceItem);
|
||||
|
|
@ -74,6 +78,6 @@ export async function executeDrop(
|
|||
return { newItemId, linkPairId: forward.pairId };
|
||||
}
|
||||
|
||||
export function getAllEntities(): EntityDescriptor[] {
|
||||
return Array.from(entities.values());
|
||||
export function getAllApps(): AppDescriptor[] {
|
||||
return Array.from(apps.values());
|
||||
}
|
||||
55
apps/manacore/apps/web/src/lib/app-registry/types.ts
Normal file
55
apps/manacore/apps/web/src/lib/app-registry/types.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Unified App Registry — Types
|
||||
*
|
||||
* Each app declares an AppDescriptor that describes identity, views,
|
||||
* and optional entity capabilities (DnD, linking, cross-module drops).
|
||||
*/
|
||||
|
||||
import type { DragType } from '@manacore/shared-ui/dnd';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnyComponent = import('svelte').Component<any, any>;
|
||||
|
||||
export interface ViewLoader {
|
||||
load: () => Promise<{ default: AnyComponent }>;
|
||||
}
|
||||
|
||||
export interface EntityDisplayData {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export interface AppDescriptor {
|
||||
// -- Identity --
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
|
||||
// -- Views --
|
||||
views: {
|
||||
list: ViewLoader;
|
||||
detail?: ViewLoader;
|
||||
};
|
||||
|
||||
// -- Entity (optional -- for DnD + linking) --
|
||||
collection?: string;
|
||||
paramKey?: string;
|
||||
dragType?: DragType;
|
||||
acceptsDropFrom?: DragType[];
|
||||
transformIncoming?: Partial<
|
||||
Record<DragType, (sourceItem: Record<string, unknown>) => Record<string, unknown>>
|
||||
>;
|
||||
createItem?: (data: Record<string, unknown>) => Promise<string>;
|
||||
getDisplayData?: (item: Record<string, unknown>) => EntityDisplayData;
|
||||
}
|
||||
|
||||
export interface DropResult {
|
||||
newItemId: string;
|
||||
linkPairId: string;
|
||||
}
|
||||
|
||||
export interface ViewProps {
|
||||
navigate: (viewName: string, params?: Record<string, unknown>) => void;
|
||||
goBack: () => void;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
type ManaRecordRef,
|
||||
type LocalManaLink,
|
||||
} from '@manacore/shared-links';
|
||||
import { getAppEntry } from '$lib/components/workbench/app-registry';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import { getApp } from '$lib/app-registry';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
interface Props {
|
||||
recordRef: ManaRecordRef;
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<span class="section-label">Verknüpfungen</span>
|
||||
<div class="links-list">
|
||||
{#each links as link (link.id)}
|
||||
{@const appEntry = getAppEntry(link.targetApp)}
|
||||
{@const appEntry = getApp(link.targetApp)}
|
||||
{@const color = link.cachedTarget?.color ?? appEntry?.color ?? '#6B7280'}
|
||||
<button class="link-item" onclick={() => openLink(link)}>
|
||||
<span class="link-dot" style="background: {color}"></span>
|
||||
|
|
|
|||
|
|
@ -6,20 +6,11 @@
|
|||
<script lang="ts">
|
||||
import { X, CaretUp, CaretDown, ArrowLeft, SpinnerGap } from '@manacore/shared-icons';
|
||||
import { PageShell } from '$lib/components/page-carousel';
|
||||
import { getAppEntry } from './app-registry';
|
||||
import { getApp, getAppByDragType, canDrop, executeDrop } from '$lib/app-registry';
|
||||
import type { Component } from 'svelte';
|
||||
import { dropTarget } from '@manacore/shared-ui/dnd';
|
||||
import {
|
||||
getEntity,
|
||||
getEntityByDragType,
|
||||
canDrop,
|
||||
executeDrop,
|
||||
ensureEntitiesRegistered,
|
||||
} from '$lib/entities';
|
||||
import type { DragPayload } from '@manacore/shared-ui/dnd';
|
||||
|
||||
ensureEntitiesRegistered();
|
||||
|
||||
interface Props {
|
||||
appId: string;
|
||||
widthPx: number;
|
||||
|
|
@ -46,18 +37,17 @@
|
|||
onMoveRight,
|
||||
}: Props = $props();
|
||||
|
||||
let appEntry = $derived(getAppEntry(appId));
|
||||
let appName = $derived(appEntry?.name ?? appId);
|
||||
let appColor = $derived(appEntry?.color ?? '#6B7280');
|
||||
let app = $derived(getApp(appId));
|
||||
let appName = $derived(app?.name ?? appId);
|
||||
let appColor = $derived(app?.color ?? '#6B7280');
|
||||
|
||||
// ── Cross-module drop target ────────────────────────────
|
||||
let targetEntity = $derived(getEntity(appId));
|
||||
let acceptedDropTypes = $derived(targetEntity?.acceptsDropFrom ?? []);
|
||||
let acceptedDropTypes = $derived(app?.acceptsDropFrom ?? []);
|
||||
|
||||
function handleCrossModuleDrop(payload: DragPayload) {
|
||||
const sourceEntity = getEntityByDragType(payload.type);
|
||||
if (!sourceEntity) return;
|
||||
executeDrop(payload.data as Record<string, unknown>, sourceEntity.appId, appId);
|
||||
const sourceApp = getAppByDragType(payload.type);
|
||||
if (!sourceApp) return;
|
||||
executeDrop(payload.data as Record<string, unknown>, sourceApp.id, appId);
|
||||
}
|
||||
|
||||
// ── List View (always loaded) ───────────────────────────
|
||||
|
|
@ -67,8 +57,8 @@
|
|||
$effect(() => {
|
||||
ListComponent = null;
|
||||
loadError = false;
|
||||
if (appEntry) {
|
||||
const loader = appEntry.views?.list?.load ?? appEntry.load;
|
||||
if (app) {
|
||||
const loader = app.views.list.load;
|
||||
loader().then(
|
||||
(mod) => (ListComponent = mod.default),
|
||||
() => (loadError = true)
|
||||
|
|
@ -120,10 +110,9 @@
|
|||
const targetId = params._targetId as string;
|
||||
if (!targetApp || !targetId) return;
|
||||
|
||||
const targetEntity = getEntity(targetApp);
|
||||
const targetAppEntry = getAppEntry(targetApp);
|
||||
const targetViewEntry = targetAppEntry?.views?.detail;
|
||||
if (!targetViewEntry || !targetEntity) {
|
||||
const targetAppDesc = getApp(targetApp);
|
||||
const targetViewEntry = targetAppDesc?.views?.detail;
|
||||
if (!targetViewEntry || !targetAppDesc?.paramKey) {
|
||||
console.warn(`No detail view registered for app "${targetApp}"`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -133,10 +122,10 @@
|
|||
...overlayStack,
|
||||
{
|
||||
viewName: 'cross-detail',
|
||||
params: { [targetEntity.paramKey]: targetId },
|
||||
params: { [targetAppDesc.paramKey!]: targetId },
|
||||
component: mod.default,
|
||||
overlayColor: targetAppEntry?.color,
|
||||
overlayTitle: targetAppEntry?.name,
|
||||
overlayColor: targetAppDesc.color,
|
||||
overlayTitle: targetAppDesc.name,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
|
@ -144,7 +133,7 @@
|
|||
}
|
||||
|
||||
// Normal detail view within the same app
|
||||
const viewEntry = appEntry?.views?.[viewName];
|
||||
const viewEntry = viewName === 'detail' ? app?.views?.detail : undefined;
|
||||
if (!viewEntry) {
|
||||
console.warn(`View "${viewName}" not registered for app "${appId}"`);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { X } from '@manacore/shared-icons';
|
||||
import { APP_REGISTRY } from './app-registry';
|
||||
import { getAllApps } from '$lib/app-registry';
|
||||
|
||||
interface Props {
|
||||
onSelect: (appId: string) => void;
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
let { onSelect, onClose, activeAppIds = [] }: Props = $props();
|
||||
|
||||
let availableApps = $derived(APP_REGISTRY.filter((app) => !activeAppIds.includes(app.id)));
|
||||
let availableApps = $derived(getAllApps().filter((app) => !activeAppIds.includes(app.id)));
|
||||
</script>
|
||||
|
||||
<div class="app-picker">
|
||||
|
|
|
|||
|
|
@ -1,238 +0,0 @@
|
|||
/**
|
||||
* App Component Registry — Maps app IDs to lazy-loaded views.
|
||||
*
|
||||
* Each entry provides a default `load` (list view) and optional named `views`
|
||||
* for in-panel navigation (detail, create, edit, etc.).
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnyComponent = import('svelte').Component<any, any>;
|
||||
|
||||
export interface ViewEntry {
|
||||
load: () => Promise<{ default: AnyComponent }>;
|
||||
}
|
||||
|
||||
export interface AppEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
/** Default view loader (list/main view). */
|
||||
load: () => Promise<{ default: AnyComponent }>;
|
||||
/** Named views for in-panel navigation. Fallback: { list: load }. */
|
||||
views?: Record<string, ViewEntry>;
|
||||
}
|
||||
|
||||
export const APP_REGISTRY: AppEntry[] = [
|
||||
{
|
||||
id: 'todo',
|
||||
name: 'Todo',
|
||||
color: '#8B5CF6',
|
||||
load: () => import('$lib/modules/todo/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/todo/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/todo/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'calendar',
|
||||
name: 'Kalender',
|
||||
color: '#3B82F6',
|
||||
load: () => import('$lib/modules/calendar/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/calendar/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/calendar/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'contacts',
|
||||
name: 'Kontakte',
|
||||
color: '#22C55E',
|
||||
load: () => import('$lib/modules/contacts/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/contacts/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/contacts/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'habits',
|
||||
name: 'Habits',
|
||||
color: '#8B5CF6',
|
||||
load: () => import('$lib/modules/habits/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'chat',
|
||||
name: 'Chat',
|
||||
color: '#6366F1',
|
||||
load: () => import('$lib/modules/chat/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'times',
|
||||
name: 'Times',
|
||||
color: '#F59E0B',
|
||||
load: () => import('$lib/modules/times/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/times/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/times/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'zitare',
|
||||
name: 'Zitare',
|
||||
color: '#EC4899',
|
||||
load: () => import('$lib/modules/zitare/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/zitare/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/zitare/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cards',
|
||||
name: 'Cards',
|
||||
color: '#EF4444',
|
||||
load: () => import('$lib/modules/cards/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/cards/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/cards/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'picture',
|
||||
name: 'Picture',
|
||||
color: '#8B5CF6',
|
||||
load: () => import('$lib/modules/picture/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'mukke',
|
||||
name: 'Mukke',
|
||||
color: '#F97316',
|
||||
load: () => import('$lib/modules/mukke/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/mukke/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/mukke/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'photos',
|
||||
name: 'Photos',
|
||||
color: '#06B6D4',
|
||||
load: () => import('$lib/modules/photos/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'storage',
|
||||
name: 'Storage',
|
||||
color: '#6B7280',
|
||||
load: () => import('$lib/modules/storage/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/storage/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/storage/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'nutriphi',
|
||||
name: 'Nutriphi',
|
||||
color: '#22C55E',
|
||||
load: () => import('$lib/modules/nutriphi/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'planta',
|
||||
name: 'Planta',
|
||||
color: '#16A34A',
|
||||
load: () => import('$lib/modules/planta/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/planta/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/planta/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'presi',
|
||||
name: 'Presi',
|
||||
color: '#A855F7',
|
||||
load: () => import('$lib/modules/presi/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/presi/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/presi/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'inventar',
|
||||
name: 'Inventar',
|
||||
color: '#78716C',
|
||||
load: () => import('$lib/modules/inventar/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/inventar/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/inventar/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memoro',
|
||||
name: 'Memoro',
|
||||
color: '#F59E0B',
|
||||
load: () => import('$lib/modules/memoro/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/memoro/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/memoro/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'questions',
|
||||
name: 'Questions',
|
||||
color: '#2563EB',
|
||||
load: () => import('$lib/modules/questions/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/questions/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/questions/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'skilltree',
|
||||
name: 'SkillTree',
|
||||
color: '#D946EF',
|
||||
load: () => import('$lib/modules/skilltree/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/skilltree/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/skilltree/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'moodlit',
|
||||
name: 'Moodlit',
|
||||
color: '#F97316',
|
||||
load: () => import('$lib/modules/moodlit/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'citycorners',
|
||||
name: 'CityCorners',
|
||||
color: '#14B8A6',
|
||||
load: () => import('$lib/modules/citycorners/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/citycorners/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/citycorners/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'uload',
|
||||
name: 'uLoad',
|
||||
color: '#0EA5E9',
|
||||
load: () => import('$lib/modules/uload/ListView.svelte'),
|
||||
views: {
|
||||
list: { load: () => import('$lib/modules/uload/ListView.svelte') },
|
||||
detail: { load: () => import('$lib/modules/uload/views/DetailView.svelte') },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'calc',
|
||||
name: 'Calc',
|
||||
color: '#6B7280',
|
||||
load: () => import('$lib/modules/calc/ListView.svelte'),
|
||||
},
|
||||
{
|
||||
id: 'playground',
|
||||
name: 'Playground',
|
||||
color: '#9CA3AF',
|
||||
load: () => import('$lib/modules/playground/ListView.svelte'),
|
||||
},
|
||||
];
|
||||
|
||||
export function getAppEntry(appId: string): AppEntry | undefined {
|
||||
return APP_REGISTRY.find((a) => a.id === appId);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/**
|
||||
* Panel Navigation Stack — Types for in-panel view navigation.
|
||||
*
|
||||
* Each workbench panel manages its own navigation stack.
|
||||
* Views are pushed/popped within the panel (list → detail → edit).
|
||||
*/
|
||||
|
||||
import type { Component } from 'svelte';
|
||||
|
||||
export interface NavFrame {
|
||||
viewName: string;
|
||||
params: Record<string, unknown>;
|
||||
component: Component | null;
|
||||
}
|
||||
|
||||
export interface ViewProps {
|
||||
navigate: (viewName: string, params?: Record<string, unknown>) => void;
|
||||
goBack: () => void;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// Types
|
||||
export type { EntityDescriptor, EntityDisplayData, DropResult } from './types';
|
||||
|
||||
// Registry
|
||||
export {
|
||||
registerEntity,
|
||||
getEntity,
|
||||
getEntityByDragType,
|
||||
canDrop,
|
||||
executeDrop,
|
||||
getAllEntities,
|
||||
} from './registry';
|
||||
|
||||
// Register module entities eagerly — these are lightweight descriptor files
|
||||
// with no heavy dependencies (stores are only called at drop time, not import time).
|
||||
import '$lib/modules/todo/entity';
|
||||
import '$lib/modules/calendar/entity';
|
||||
import '$lib/modules/contacts/entity';
|
||||
|
||||
// Re-export for consumers that previously used lazy registration
|
||||
export function ensureEntitiesRegistered(): void {
|
||||
// No-op — entities are now registered at import time
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* Entity Descriptor System — Types
|
||||
*
|
||||
* Each module declares an EntityDescriptor that describes how its items
|
||||
* can be displayed, dragged, dropped, and created from other modules.
|
||||
*/
|
||||
|
||||
import type { DragType } from '@manacore/shared-ui/dnd';
|
||||
|
||||
export interface EntityDisplayData {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export interface EntityDescriptor {
|
||||
appId: string;
|
||||
collection: string;
|
||||
|
||||
/** Param key name used in DetailView (e.g. 'taskId', 'eventId', 'contactId'). */
|
||||
paramKey: string;
|
||||
|
||||
/** How to display an item in link previews and drag ghosts. */
|
||||
getDisplayData: (item: Record<string, unknown>) => EntityDisplayData;
|
||||
|
||||
/** DragType used when dragging items from this module. */
|
||||
dragType: DragType;
|
||||
|
||||
/** Which DragTypes this module accepts as drop targets. */
|
||||
acceptsDropFrom?: DragType[];
|
||||
|
||||
/** Transform incoming items from other modules into this module's create shape. */
|
||||
transformIncoming?: Partial<
|
||||
Record<DragType, (sourceItem: Record<string, unknown>) => Record<string, unknown>>
|
||||
>;
|
||||
|
||||
/** Create a new item in this module. Returns the new item's ID. */
|
||||
createItem?: (data: Record<string, unknown>) => Promise<string>;
|
||||
}
|
||||
|
||||
export interface DropResult {
|
||||
newItemId: string;
|
||||
linkPairId: string;
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
import type { LocalEvent } from './types';
|
||||
import { eventsStore } from './stores/events.svelte';
|
||||
import { Plus } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import { dropTarget, dragSource } from '@manacore/shared-ui/dnd';
|
||||
import type { TagDragData } from '@manacore/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
import { registerEntity } from '$lib/entities/registry';
|
||||
import type { EntityDescriptor } from '$lib/entities/types';
|
||||
|
||||
const calendarEntity: EntityDescriptor = {
|
||||
appId: 'calendar',
|
||||
collection: 'events',
|
||||
paramKey: 'eventId',
|
||||
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.title as string) || 'Termin',
|
||||
subtitle: item.startDate
|
||||
? new Date(item.startDate as string).toLocaleDateString('de', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: undefined,
|
||||
}),
|
||||
|
||||
dragType: 'event',
|
||||
acceptsDropFrom: ['task', 'contact'],
|
||||
|
||||
transformIncoming: {
|
||||
task: (source) => {
|
||||
const dueDate = (source.dueDate as string) || new Date().toISOString();
|
||||
const start = new Date(dueDate);
|
||||
const end = new Date(start.getTime() + 60 * 60 * 1000);
|
||||
return {
|
||||
title: source.title as string,
|
||||
startTime: start.toISOString(),
|
||||
endTime: end.toISOString(),
|
||||
description: source.description as string | undefined,
|
||||
};
|
||||
},
|
||||
contact: (source) => {
|
||||
const name = [source.firstName, source.lastName].filter(Boolean).join(' ');
|
||||
const now = new Date();
|
||||
const end = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
return {
|
||||
title: `Treffen mit ${name}`,
|
||||
startTime: now.toISOString(),
|
||||
endTime: end.toISOString(),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
createItem: async (data) => {
|
||||
// Lazy imports to avoid circular dependency at registration time
|
||||
const { db } = await import('$lib/data/database');
|
||||
const { eventsStore } = await import('./stores/events.svelte');
|
||||
|
||||
const calendars = await db.table('calendars').toArray();
|
||||
const defaultCal = calendars.find((c: Record<string, unknown>) => !c.deletedAt);
|
||||
const calendarId = (defaultCal?.id as string) ?? 'default';
|
||||
|
||||
const result = await eventsStore.createEvent({
|
||||
calendarId,
|
||||
title: data.title as string,
|
||||
startTime: data.startTime as string,
|
||||
endTime: data.endTime as string,
|
||||
description: (data.description as string) ?? undefined,
|
||||
});
|
||||
|
||||
if (!result.success || !result.data) throw new Error(result.error || 'Failed to create event');
|
||||
return result.data.id;
|
||||
},
|
||||
};
|
||||
|
||||
registerEntity(calendarEntity);
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { eventsStore } from '../stores/events.svelte';
|
||||
import { Trash, MapPin, Clock, X } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalEvent } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalDeck, LocalCard } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { deckStore } from '../stores/decks.svelte';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalDeck, LocalCard } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import type { LocalLocation, LocalFavorite } from './types';
|
||||
import { CATEGORY_COLORS } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { favoritesStore } from '../stores/favorites.svelte';
|
||||
import { Star, Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalLocation, LocalFavorite } from '../types';
|
||||
import { CATEGORY_COLORS } from '../types';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import type { LocalContact } from './types';
|
||||
import { contactsStore } from './stores/contacts.svelte';
|
||||
import { Plus, Star } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import { dropTarget, dragSource } from '@manacore/shared-ui/dnd';
|
||||
import type { TagDragData } from '@manacore/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
import { registerEntity } from '$lib/entities/registry';
|
||||
import type { EntityDescriptor } from '$lib/entities/types';
|
||||
|
||||
const contactsEntity: EntityDescriptor = {
|
||||
appId: 'contacts',
|
||||
collection: 'contacts',
|
||||
paramKey: 'contactId',
|
||||
|
||||
getDisplayData: (item) => {
|
||||
const name = [item.firstName, item.lastName].filter(Boolean).join(' ');
|
||||
return {
|
||||
title: name || (item.email as string) || 'Kontakt',
|
||||
subtitle: (item.company as string) ?? undefined,
|
||||
};
|
||||
},
|
||||
|
||||
dragType: 'contact',
|
||||
// Contacts are drag sources only — dropping onto contacts doesn't create a new contact
|
||||
};
|
||||
|
||||
registerEntity(contactsEntity);
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
Globe,
|
||||
X,
|
||||
} from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalContact } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { registerEntity } from '$lib/entities/registry';
|
||||
import { financeStore } from './stores/finance.svelte';
|
||||
import type { EntityDescriptor } from '$lib/entities/types';
|
||||
|
||||
const financeEntity: EntityDescriptor = {
|
||||
appId: 'finance',
|
||||
collection: 'transactions',
|
||||
paramKey: 'transactionId',
|
||||
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.description as string) || 'Transaktion',
|
||||
subtitle: item.amount ? `${item.type === 'income' ? '+' : '-'}${item.amount}` : undefined,
|
||||
}),
|
||||
|
||||
dragType: 'transaction',
|
||||
acceptsDropFrom: [],
|
||||
|
||||
createItem: async (data) => {
|
||||
const tx = await financeStore.addTransaction({
|
||||
type: 'expense',
|
||||
amount: (data.amount as number) ?? 0,
|
||||
description: (data.title as string) ?? (data.description as string) ?? '',
|
||||
});
|
||||
return tx.id;
|
||||
},
|
||||
};
|
||||
|
||||
registerEntity(financeEntity);
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
} from './queries';
|
||||
import { habitsStore } from './stores/habits.svelte';
|
||||
import type { Habit, HabitLog } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import { registerEntity } from '$lib/entities/registry';
|
||||
import { habitsStore } from './stores/habits.svelte';
|
||||
import type { EntityDescriptor } from '$lib/entities/types';
|
||||
|
||||
const habitsEntity: EntityDescriptor = {
|
||||
appId: 'habits',
|
||||
collection: 'habits',
|
||||
paramKey: 'habitId',
|
||||
|
||||
getDisplayData: (item) => ({
|
||||
title: `${item.emoji as string} ${item.title as string}`,
|
||||
subtitle: undefined,
|
||||
}),
|
||||
|
||||
dragType: 'habit',
|
||||
acceptsDropFrom: ['task'],
|
||||
|
||||
transformIncoming: {
|
||||
task: (source) => ({
|
||||
title: source.title as string,
|
||||
emoji: '\ud83d\udcaa',
|
||||
color: '#6366f1',
|
||||
}),
|
||||
},
|
||||
|
||||
createItem: async (data) => {
|
||||
const habit = await habitsStore.createHabit({
|
||||
title: data.title as string,
|
||||
emoji: (data.emoji as string) ?? '\u2b50',
|
||||
color: (data.color as string) ?? '#6366f1',
|
||||
});
|
||||
return habit.id;
|
||||
},
|
||||
};
|
||||
|
||||
registerEntity(habitsEntity);
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalCollection, LocalItem } from './types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { collectionsStore } from '../stores/collections.svelte';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalCollection, LocalItem } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalMemo } from './types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { memosStore } from '../stores/memos.svelte';
|
||||
import { Trash, PushPin } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalMemo, ProcessingStatus } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalSong, LocalPlaylist } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { libraryStore } from '../stores/library.svelte';
|
||||
import { Heart, Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalSong } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { registerEntity } from '$lib/entities/registry';
|
||||
import { notesStore } from './stores/notes.svelte';
|
||||
import type { EntityDescriptor } from '$lib/entities/types';
|
||||
|
||||
const notesEntity: EntityDescriptor = {
|
||||
appId: 'notes',
|
||||
collection: 'notes',
|
||||
paramKey: 'noteId',
|
||||
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.title as string) || 'Notiz',
|
||||
subtitle: undefined,
|
||||
}),
|
||||
|
||||
dragType: 'note',
|
||||
acceptsDropFrom: ['task', 'contact'],
|
||||
|
||||
transformIncoming: {
|
||||
task: (source) => ({
|
||||
title: source.title as string,
|
||||
content: (source.description as string) ?? '',
|
||||
}),
|
||||
contact: (source) => ({
|
||||
title: `${[source.firstName, source.lastName].filter(Boolean).join(' ')}`,
|
||||
content: `Kontakt: ${[source.firstName, source.lastName].filter(Boolean).join(' ')}`,
|
||||
}),
|
||||
},
|
||||
|
||||
createItem: async (data) => {
|
||||
const note = await notesStore.createNote({
|
||||
title: data.title as string,
|
||||
content: (data.content as string) ?? '',
|
||||
});
|
||||
return note.id;
|
||||
},
|
||||
};
|
||||
|
||||
registerEntity(notesEntity);
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalPlant, LocalWateringSchedule } from './types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalPlant, HealthStatus, LightLevel } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalDeck, LocalSlide } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { decksStore } from '../stores/decks.svelte';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalDeck, LocalSlide } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalQuestion, LocalCollection } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalQuestion, QuestionStatus, QuestionPriority, ResearchDepth } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<script lang="ts">
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalSkill, LocalActivity } from './types';
|
||||
import { LEVEL_NAMES, BRANCH_INFO, xpProgress, type SkillBranch } from './types';
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { skillStore } from '../stores/skills.svelte';
|
||||
import { Trash, Lightning } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalSkill, SkillBranch } from '../types';
|
||||
import { BRANCH_INFO, LEVEL_NAMES, xpProgress, xpForNextLevel } from '../types';
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalFile, LocalFolder } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { filesStore } from '../stores/files.svelte';
|
||||
import { Heart, Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalFile } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import { timerStore } from '$lib/modules/times/stores/timer.svelte';
|
||||
import { formatDuration } from '$lib/modules/times/queries';
|
||||
import { Play, Stop } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalTimeEntry, LocalProject } from './types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalTimeEntry, LocalProject, LocalClient } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
} from './queries';
|
||||
import { tasksStore } from './stores/tasks.svelte';
|
||||
import { Circle, Check } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import { dropTarget, dragSource } from '@manacore/shared-ui/dnd';
|
||||
import type { TagDragData } from '@manacore/shared-ui/dnd';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import { registerEntity } from '$lib/entities/registry';
|
||||
import type { EntityDescriptor } from '$lib/entities/types';
|
||||
|
||||
const todoEntity: EntityDescriptor = {
|
||||
appId: 'todo',
|
||||
collection: 'tasks',
|
||||
paramKey: 'taskId',
|
||||
|
||||
getDisplayData: (item) => ({
|
||||
title: (item.title as string) || 'Aufgabe',
|
||||
subtitle: item.dueDate ? new Date(item.dueDate as string).toLocaleDateString('de') : undefined,
|
||||
}),
|
||||
|
||||
dragType: 'task',
|
||||
acceptsDropFrom: ['event', 'contact'],
|
||||
|
||||
transformIncoming: {
|
||||
event: (source) => ({
|
||||
title: source.title as string,
|
||||
dueDate: source.startDate as string,
|
||||
description: source.description as string | undefined,
|
||||
}),
|
||||
contact: (source) => ({
|
||||
title: `Kontaktieren: ${[source.firstName, source.lastName].filter(Boolean).join(' ')}`,
|
||||
}),
|
||||
},
|
||||
|
||||
createItem: async (data) => {
|
||||
// Lazy import to avoid circular dependency at registration time
|
||||
const { tasksStore } = await import('./stores/tasks.svelte');
|
||||
const task = await tasksStore.createTask(
|
||||
data as { title: string; dueDate?: string; description?: string }
|
||||
);
|
||||
return task.id;
|
||||
},
|
||||
};
|
||||
|
||||
registerEntity(todoEntity);
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { tasksStore } from '../stores/tasks.svelte';
|
||||
import { Check, Trash, X } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalTask, TaskPriority } from '../types';
|
||||
import { useAllTags, getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import LinkedItems from '$lib/components/links/LinkedItems.svelte';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import type { LocalLink, LocalFolder } from './types';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import { liveQuery } from 'dexie';
|
||||
import { db } from '$lib/data/database';
|
||||
import { Trash } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalLink } from '../types';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
import { dropTarget } from '@manacore/shared-ui/dnd';
|
||||
import type { TagDragData } from '@manacore/shared-ui/dnd';
|
||||
import { getTagsByIds } from '$lib/stores/tags.svelte';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalFavorite } from './types';
|
||||
import type { Quote } from '@zitare/content';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import { favoritesStore } from '$lib/modules/zitare/stores/favorites.svelte';
|
||||
import { isFavorite as checkIsFavorite, type Favorite } from '$lib/modules/zitare/queries';
|
||||
import { Heart, ShareNetwork, Info } from '@manacore/shared-icons';
|
||||
import type { ViewProps } from '$lib/components/workbench/nav-stack';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
import type { LocalFavorite } from '../types';
|
||||
import { QUOTES, type Quote, type Category } from '@zitare/content';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,72 +1,50 @@
|
|||
/**
|
||||
* Split-Screen App Registry
|
||||
*
|
||||
* Lazy-import registry for all app modules.
|
||||
* Each app has an ListView.svelte component that renders in split-screen.
|
||||
* Delegates to the unified app registry for component loading and labels.
|
||||
*/
|
||||
|
||||
const APP_COMPONENTS = {
|
||||
todo: () => import('$lib/modules/todo/ListView.svelte'),
|
||||
calendar: () => import('$lib/modules/calendar/ListView.svelte'),
|
||||
contacts: () => import('$lib/modules/contacts/ListView.svelte'),
|
||||
chat: () => import('$lib/modules/chat/ListView.svelte'),
|
||||
picture: () => import('$lib/modules/picture/ListView.svelte'),
|
||||
cards: () => import('$lib/modules/cards/ListView.svelte'),
|
||||
zitare: () => import('$lib/modules/zitare/ListView.svelte'),
|
||||
mukke: () => import('$lib/modules/mukke/ListView.svelte'),
|
||||
storage: () => import('$lib/modules/storage/ListView.svelte'),
|
||||
presi: () => import('$lib/modules/presi/ListView.svelte'),
|
||||
inventar: () => import('$lib/modules/inventar/ListView.svelte'),
|
||||
photos: () => import('$lib/modules/photos/ListView.svelte'),
|
||||
skilltree: () => import('$lib/modules/skilltree/ListView.svelte'),
|
||||
citycorners: () => import('$lib/modules/citycorners/ListView.svelte'),
|
||||
times: () => import('$lib/modules/times/ListView.svelte'),
|
||||
context: () => import('$lib/modules/context/ListView.svelte'),
|
||||
questions: () => import('$lib/modules/questions/ListView.svelte'),
|
||||
nutriphi: () => import('$lib/modules/nutriphi/ListView.svelte'),
|
||||
planta: () => import('$lib/modules/planta/ListView.svelte'),
|
||||
uload: () => import('$lib/modules/uload/ListView.svelte'),
|
||||
calc: () => import('$lib/modules/calc/ListView.svelte'),
|
||||
moodlit: () => import('$lib/modules/moodlit/ListView.svelte'),
|
||||
memoro: () => import('$lib/modules/memoro/ListView.svelte'),
|
||||
playground: () => import('$lib/modules/playground/ListView.svelte'),
|
||||
};
|
||||
import { getApp, getAllApps } from '$lib/app-registry';
|
||||
|
||||
export type SplitAppId = keyof typeof APP_COMPONENTS;
|
||||
const SPLIT_APP_ID_LIST = [
|
||||
'todo',
|
||||
'calendar',
|
||||
'contacts',
|
||||
'chat',
|
||||
'picture',
|
||||
'cards',
|
||||
'zitare',
|
||||
'mukke',
|
||||
'storage',
|
||||
'presi',
|
||||
'inventar',
|
||||
'photos',
|
||||
'skilltree',
|
||||
'citycorners',
|
||||
'times',
|
||||
'context',
|
||||
'questions',
|
||||
'nutriphi',
|
||||
'planta',
|
||||
'uload',
|
||||
'calc',
|
||||
'moodlit',
|
||||
'memoro',
|
||||
'playground',
|
||||
] as const;
|
||||
|
||||
export const SPLIT_APP_IDS = Object.keys(APP_COMPONENTS) as SplitAppId[];
|
||||
export type SplitAppId = (typeof SPLIT_APP_ID_LIST)[number];
|
||||
|
||||
/** Display names for each app (German UI). */
|
||||
export const SPLIT_APP_LABELS: Record<SplitAppId, string> = {
|
||||
todo: 'Todo',
|
||||
calendar: 'Kalender',
|
||||
contacts: 'Kontakte',
|
||||
chat: 'Chat',
|
||||
picture: 'Picture',
|
||||
cards: 'Cards',
|
||||
zitare: 'Zitare',
|
||||
mukke: 'Mukke',
|
||||
storage: 'Storage',
|
||||
presi: 'Presi',
|
||||
inventar: 'Inventar',
|
||||
photos: 'Fotos',
|
||||
skilltree: 'SkillTree',
|
||||
citycorners: 'CityCorners',
|
||||
times: 'Times & Clock',
|
||||
context: 'Context',
|
||||
questions: 'Questions',
|
||||
nutriphi: 'NutriPhi',
|
||||
planta: 'Planta',
|
||||
uload: 'uLoad',
|
||||
calc: 'Calc',
|
||||
moodlit: 'Moodlit',
|
||||
memoro: 'Memoro',
|
||||
playground: 'Playground',
|
||||
};
|
||||
export const SPLIT_APP_IDS = SPLIT_APP_ID_LIST as readonly SplitAppId[];
|
||||
|
||||
/** Display names for each app (from unified registry). */
|
||||
export const SPLIT_APP_LABELS: Record<SplitAppId, string> = Object.fromEntries(
|
||||
SPLIT_APP_ID_LIST.map((id) => [id, getApp(id)?.name ?? id])
|
||||
) as Record<SplitAppId, string>;
|
||||
|
||||
export async function loadAppComponent(appId: string) {
|
||||
const loader = APP_COMPONENTS[appId as SplitAppId];
|
||||
if (!loader) return null;
|
||||
const module = await loader();
|
||||
const app = getApp(appId);
|
||||
if (!app) return null;
|
||||
const module = await app.views.list.load();
|
||||
return module.default;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue