mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-27 14:37:43 +02:00
feat(manacore/web): workbench with app pages carousel on home
Replace the static app-grid home page with a workbench carousel where each app renders as a paper-sheet page that can be minimized, maximized, reordered, and resized: - AppPage.svelte: paper-sheet shell with lazy-loaded app modules - AppPagePicker.svelte: picker showing all 23 available apps - App component registry: maps appId to dynamic AppView imports - Horizontal fokus-track carousel with snap-scroll - Edit FAB with width pills (S/M/L/XL) - Minimized tabs bar for collapsed apps - Drag-and-drop reordering + arrow buttons in edit mode - Workbench state persisted to IndexedDB via shared-stores - Default layout: Todo + Calendar + Contacts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3d124f04a4
commit
f2af192172
4 changed files with 1249 additions and 428 deletions
|
|
@ -0,0 +1,357 @@
|
||||||
|
<!--
|
||||||
|
AppPage — Paper-sheet wrapper for any app in the workbench carousel.
|
||||||
|
Lazy-loads the app's AppView component and renders it inside the page shell.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
X,
|
||||||
|
Minus,
|
||||||
|
DotsSixVertical,
|
||||||
|
CornersOut,
|
||||||
|
CornersIn,
|
||||||
|
ArrowLeft,
|
||||||
|
ArrowRight,
|
||||||
|
SpinnerGap,
|
||||||
|
} from '@manacore/shared-icons';
|
||||||
|
import { getAppEntry } from './app-registry';
|
||||||
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
appId: string;
|
||||||
|
pageWidth: string;
|
||||||
|
maximized?: boolean;
|
||||||
|
editMode?: boolean;
|
||||||
|
isFirst?: boolean;
|
||||||
|
isLast?: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onMinimize?: () => void;
|
||||||
|
onMaximize?: () => void;
|
||||||
|
onMoveLeft?: () => void;
|
||||||
|
onMoveRight?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
appId,
|
||||||
|
pageWidth,
|
||||||
|
maximized = false,
|
||||||
|
editMode = false,
|
||||||
|
isFirst = false,
|
||||||
|
isLast = false,
|
||||||
|
onClose,
|
||||||
|
onMinimize,
|
||||||
|
onMaximize,
|
||||||
|
onMoveLeft,
|
||||||
|
onMoveRight,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let appEntry = $derived(getAppEntry(appId));
|
||||||
|
let appName = $derived(appEntry?.name ?? appId);
|
||||||
|
let appColor = $derived(appEntry?.color ?? '#6B7280');
|
||||||
|
|
||||||
|
// Lazy-load app component
|
||||||
|
let AppComponent = $state<Component | null>(null);
|
||||||
|
let loadError = $state(false);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
AppComponent = null;
|
||||||
|
loadError = false;
|
||||||
|
if (appEntry) {
|
||||||
|
appEntry.load().then(
|
||||||
|
(mod) => (AppComponent = mod.default),
|
||||||
|
() => (loadError = true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="app-page"
|
||||||
|
class:maximized
|
||||||
|
class:editing={editMode}
|
||||||
|
style="width: {maximized ? '100%' : pageWidth}"
|
||||||
|
>
|
||||||
|
<div class="drag-handle-bar">
|
||||||
|
<span class="drag-handle"><DotsSixVertical size={14} /></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit controls -->
|
||||||
|
{#if editMode}
|
||||||
|
<div class="edit-controls">
|
||||||
|
<div class="move-btns">
|
||||||
|
{#if !isFirst && onMoveLeft}
|
||||||
|
<button class="edit-btn" onclick={onMoveLeft} title="Nach links">
|
||||||
|
<ArrowLeft size={14} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{#if !isLast && onMoveRight}
|
||||||
|
<button class="edit-btn" onclick={onMoveRight} title="Nach rechts">
|
||||||
|
<ArrowRight size={14} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<button class="edit-btn delete-btn" onclick={onClose} title="App entfernen">
|
||||||
|
<X size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<span class="app-dot" style="background-color: {appColor}"></span>
|
||||||
|
<span class="app-name">{appName}</span>
|
||||||
|
</div>
|
||||||
|
{#if !editMode}
|
||||||
|
<div class="header-actions">
|
||||||
|
{#if onMinimize}
|
||||||
|
<button class="header-btn" onclick={onMinimize} title="Minimieren">
|
||||||
|
<Minus size={14} />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
{#if onMaximize}
|
||||||
|
<button
|
||||||
|
class="header-btn"
|
||||||
|
onclick={onMaximize}
|
||||||
|
title={maximized ? 'Verkleinern' : 'Maximieren'}
|
||||||
|
>
|
||||||
|
{#if maximized}<CornersIn size={14} />{:else}<CornersOut size={14} />{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<button class="header-btn" onclick={onClose} title="Schließen">
|
||||||
|
<X size={14} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- App content -->
|
||||||
|
<div class="page-body">
|
||||||
|
{#if loadError}
|
||||||
|
<div class="load-state">
|
||||||
|
<p>App konnte nicht geladen werden</p>
|
||||||
|
</div>
|
||||||
|
{:else if AppComponent}
|
||||||
|
<AppComponent />
|
||||||
|
{:else}
|
||||||
|
<div class="load-state">
|
||||||
|
<SpinnerGap size={24} class="spinner" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app-page {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-height: 60vh;
|
||||||
|
background: #fffef5;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.08),
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.04);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: fadeIn 0.25s ease-out;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
:global(.dark) .app-page {
|
||||||
|
background-color: #252220;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
.app-page.editing {
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(139, 92, 246, 0.12),
|
||||||
|
0 0 0 2px rgba(139, 92, 246, 0.3);
|
||||||
|
}
|
||||||
|
:global(.dark) .app-page.editing {
|
||||||
|
box-shadow:
|
||||||
|
0 2px 12px rgba(139, 92, 246, 0.2),
|
||||||
|
0 0 0 2px rgba(139, 92, 246, 0.4);
|
||||||
|
}
|
||||||
|
.app-page.maximized {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 100vh;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
animation: fadeInScale 0.2s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes fadeInScale {
|
||||||
|
from {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.25rem 0 0;
|
||||||
|
}
|
||||||
|
.drag-handle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 14px;
|
||||||
|
color: #d1d5db;
|
||||||
|
cursor: grab;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: color 0.15s;
|
||||||
|
}
|
||||||
|
.drag-handle:hover {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
.drag-handle:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
:global(.dark) .drag-handle {
|
||||||
|
color: #3f3b38;
|
||||||
|
}
|
||||||
|
:global(.dark) .drag-handle:hover {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit controls */
|
||||||
|
.edit-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
:global(.dark) .edit-controls {
|
||||||
|
border-bottom-color: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
.move-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
.edit-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.edit-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
:global(.dark) .edit-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
.delete-btn:hover {
|
||||||
|
color: #ef4444;
|
||||||
|
background: rgba(239, 68, 68, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.app-dot {
|
||||||
|
width: 0.625rem;
|
||||||
|
height: 0.625rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.app-name {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
:global(.dark) .app-name {
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.125rem;
|
||||||
|
}
|
||||||
|
.header-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.header-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
:global(.dark) .header-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
.page-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-state :global(.spinner) {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
<!--
|
||||||
|
AppPagePicker — Shows available apps to add as pages to the workbench.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { X } from '@manacore/shared-icons';
|
||||||
|
import { APP_REGISTRY } from './app-registry';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSelect: (appId: string) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
activeAppIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let { onSelect, onClose, activeAppIds = [] }: Props = $props();
|
||||||
|
|
||||||
|
let availableApps = $derived(APP_REGISTRY.filter((app) => !activeAppIds.includes(app.id)));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="app-picker">
|
||||||
|
<div class="picker-header">
|
||||||
|
<h3 class="picker-title">App hinzufügen</h3>
|
||||||
|
<button class="close-btn" onclick={onClose} title="Schließen"><X size={16} /></button>
|
||||||
|
</div>
|
||||||
|
<div class="picker-list">
|
||||||
|
{#each availableApps as app, i (app.id)}
|
||||||
|
{#if i > 0}<div class="divider"></div>{/if}
|
||||||
|
<button class="app-option" onclick={() => onSelect(app.id)}>
|
||||||
|
<div class="app-dot" style="background-color: {app.color}"></div>
|
||||||
|
<span class="app-name">{app.name}</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if availableApps.length === 0}
|
||||||
|
<div class="empty-state"><p>Alle Apps sind bereits geöffnet</p></div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app-picker {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: min(300px, 85vw);
|
||||||
|
min-height: 60vh;
|
||||||
|
max-height: 80vh;
|
||||||
|
background: #fffef5;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.08),
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.04);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
animation: slideIn 0.25s ease-out;
|
||||||
|
}
|
||||||
|
:global(.dark) .app-picker {
|
||||||
|
background-color: #252220;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.25),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.picker-title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
:global(.dark) .picker-title {
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
.close-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.close-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
:global(.dark) .close-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
height: 1px;
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
:global(.dark) .divider {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.625rem 0.5rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
transition: background 0.15s;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.app-option:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
:global(.dark) .app-option:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
:global(.dark) .app-name {
|
||||||
|
color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.empty-state p {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
/**
|
||||||
|
* App Component Registry — Maps app IDs to lazy-loaded AppView components.
|
||||||
|
*
|
||||||
|
* Each entry provides the dynamic import for embedding in the workbench carousel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
|
export interface AppEntry {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
load: () => Promise<{ default: Component }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const APP_REGISTRY: AppEntry[] = [
|
||||||
|
{
|
||||||
|
id: 'todo',
|
||||||
|
name: 'Todo',
|
||||||
|
color: '#8B5CF6',
|
||||||
|
load: () => import('$lib/modules/todo/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'calendar',
|
||||||
|
name: 'Kalender',
|
||||||
|
color: '#3B82F6',
|
||||||
|
load: () => import('$lib/modules/calendar/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contacts',
|
||||||
|
name: 'Kontakte',
|
||||||
|
color: '#22C55E',
|
||||||
|
load: () => import('$lib/modules/contacts/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chat',
|
||||||
|
name: 'Chat',
|
||||||
|
color: '#6366F1',
|
||||||
|
load: () => import('$lib/modules/chat/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'times',
|
||||||
|
name: 'Times',
|
||||||
|
color: '#F59E0B',
|
||||||
|
load: () => import('$lib/modules/times/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'zitare',
|
||||||
|
name: 'Zitare',
|
||||||
|
color: '#EC4899',
|
||||||
|
load: () => import('$lib/modules/zitare/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cards',
|
||||||
|
name: 'Cards',
|
||||||
|
color: '#EF4444',
|
||||||
|
load: () => import('$lib/modules/cards/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'picture',
|
||||||
|
name: 'Picture',
|
||||||
|
color: '#8B5CF6',
|
||||||
|
load: () => import('$lib/modules/picture/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mukke',
|
||||||
|
name: 'Mukke',
|
||||||
|
color: '#F97316',
|
||||||
|
load: () => import('$lib/modules/mukke/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'photos',
|
||||||
|
name: 'Photos',
|
||||||
|
color: '#06B6D4',
|
||||||
|
load: () => import('$lib/modules/photos/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'storage',
|
||||||
|
name: 'Storage',
|
||||||
|
color: '#6B7280',
|
||||||
|
load: () => import('$lib/modules/storage/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'nutriphi',
|
||||||
|
name: 'Nutriphi',
|
||||||
|
color: '#22C55E',
|
||||||
|
load: () => import('$lib/modules/nutriphi/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'planta',
|
||||||
|
name: 'Planta',
|
||||||
|
color: '#16A34A',
|
||||||
|
load: () => import('$lib/modules/planta/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'presi',
|
||||||
|
name: 'Presi',
|
||||||
|
color: '#A855F7',
|
||||||
|
load: () => import('$lib/modules/presi/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'inventar',
|
||||||
|
name: 'Inventar',
|
||||||
|
color: '#78716C',
|
||||||
|
load: () => import('$lib/modules/inventar/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'memoro',
|
||||||
|
name: 'Memoro',
|
||||||
|
color: '#F59E0B',
|
||||||
|
load: () => import('$lib/modules/memoro/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'questions',
|
||||||
|
name: 'Questions',
|
||||||
|
color: '#2563EB',
|
||||||
|
load: () => import('$lib/modules/questions/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'skilltree',
|
||||||
|
name: 'SkillTree',
|
||||||
|
color: '#D946EF',
|
||||||
|
load: () => import('$lib/modules/skilltree/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'moodlit',
|
||||||
|
name: 'Moodlit',
|
||||||
|
color: '#F97316',
|
||||||
|
load: () => import('$lib/modules/moodlit/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'citycorners',
|
||||||
|
name: 'CityCorners',
|
||||||
|
color: '#14B8A6',
|
||||||
|
load: () => import('$lib/modules/citycorners/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uload',
|
||||||
|
name: 'uLoad',
|
||||||
|
color: '#0EA5E9',
|
||||||
|
load: () => import('$lib/modules/uload/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'calc',
|
||||||
|
name: 'Calc',
|
||||||
|
color: '#6B7280',
|
||||||
|
load: () => import('$lib/modules/calc/AppView.svelte'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'playground',
|
||||||
|
name: 'Playground',
|
||||||
|
color: '#9CA3AF',
|
||||||
|
load: () => import('$lib/modules/playground/AppView.svelte'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getAppEntry(appId: string): AppEntry | undefined {
|
||||||
|
return APP_REGISTRY.find((a) => a.id === appId);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue