Merge branch 'dev-1' into dev

This commit is contained in:
Wuesteon 2025-12-05 17:57:26 +01:00
commit d41d060bb3
1770 changed files with 168028 additions and 31031 deletions

View file

@ -0,0 +1,201 @@
/**
* App Routes Configuration
*
* Defines available start pages for each app in the ecosystem.
* Used by the start page selector in global settings.
*/
/**
* Route definition with i18n label
*/
export interface AppRoute {
/** Route path (e.g., '/stopwatch') */
path: string;
/** i18n key for the label (e.g., 'nav.stopwatch') */
labelKey: string;
/** Optional icon name */
icon?: string;
}
/**
* App route configuration
*/
export interface AppRouteConfig {
/** App identifier */
appId: string;
/** Default start route (used when no preference set) */
defaultRoute: string;
/** Available routes that can be set as start page */
availableRoutes: AppRoute[];
}
/**
* Route configurations for all apps
*/
export const APP_ROUTES: Record<string, AppRouteConfig> = {
clock: {
appId: 'clock',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.dashboard', icon: 'home' },
{ path: '/alarms', labelKey: 'nav.alarms', icon: 'alarm' },
{ path: '/timers', labelKey: 'nav.timers', icon: 'timer' },
{ path: '/stopwatch', labelKey: 'nav.stopwatch', icon: 'stopwatch' },
{ path: '/pomodoro', labelKey: 'nav.pomodoro', icon: 'target' },
{ path: '/world-clock', labelKey: 'nav.worldClock', icon: 'globe' },
{ path: '/life', labelKey: 'nav.lifeClock', icon: 'heart' },
],
},
calendar: {
appId: 'calendar',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.month', icon: 'calendar' },
{ path: '/agenda', labelKey: 'nav.agenda', icon: 'list' },
],
},
contacts: {
appId: 'contacts',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.contacts', icon: 'users' },
{ path: '/groups', labelKey: 'nav.groups', icon: 'folder' },
{ path: '/favorites', labelKey: 'nav.favorites', icon: 'star' },
],
},
mail: {
appId: 'mail',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.inbox', icon: 'inbox' },
{ path: '/sent', labelKey: 'nav.sent', icon: 'send' },
{ path: '/drafts', labelKey: 'nav.drafts', icon: 'file' },
{ path: '/starred', labelKey: 'nav.starred', icon: 'star' },
],
},
todo: {
appId: 'todo',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.all', icon: 'list' },
{ path: '/today', labelKey: 'nav.today', icon: 'calendar' },
{ path: '/upcoming', labelKey: 'nav.upcoming', icon: 'clock' },
{ path: '/completed', labelKey: 'nav.completed', icon: 'check' },
],
},
storage: {
appId: 'storage',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.home', icon: 'home' },
{ path: '/files', labelKey: 'nav.files', icon: 'folder' },
{ path: '/favorites', labelKey: 'nav.favorites', icon: 'star' },
{ path: '/shared', labelKey: 'nav.shared', icon: 'share' },
],
},
chat: {
appId: 'chat',
defaultRoute: '/chat',
availableRoutes: [
{ path: '/chat', labelKey: 'nav.chat', icon: 'message' },
{ path: '/spaces', labelKey: 'nav.spaces', icon: 'folder' },
{ path: '/templates', labelKey: 'nav.templates', icon: 'file' },
{ path: '/documents', labelKey: 'nav.documents', icon: 'document' },
],
},
picture: {
appId: 'picture',
defaultRoute: '/app/gallery',
availableRoutes: [
{ path: '/app/gallery', labelKey: 'nav.gallery', icon: 'image' },
{ path: '/app/generate', labelKey: 'nav.generate', icon: 'sparkle' },
{ path: '/app/board', labelKey: 'nav.board', icon: 'grid' },
{ path: '/app/explore', labelKey: 'nav.explore', icon: 'compass' },
],
},
manadeck: {
appId: 'manadeck',
defaultRoute: '/decks',
availableRoutes: [
{ path: '/decks', labelKey: 'nav.decks', icon: 'layers' },
{ path: '/explore', labelKey: 'nav.explore', icon: 'compass' },
{ path: '/progress', labelKey: 'nav.progress', icon: 'trending' },
],
},
zitare: {
appId: 'zitare',
defaultRoute: '/',
availableRoutes: [
{ path: '/', labelKey: 'nav.home', icon: 'home' },
{ path: '/quotes', labelKey: 'nav.quotes', icon: 'quote' },
{ path: '/favorites', labelKey: 'nav.favorites', icon: 'star' },
{ path: '/authors', labelKey: 'nav.authors', icon: 'users' },
{ path: '/lists', labelKey: 'nav.lists', icon: 'list' },
],
},
presi: {
appId: 'presi',
defaultRoute: '/',
availableRoutes: [{ path: '/', labelKey: 'nav.home', icon: 'home' }],
},
manacore: {
appId: 'manacore',
defaultRoute: '/',
availableRoutes: [{ path: '/', labelKey: 'nav.dashboard', icon: 'home' }],
},
};
/**
* Get the start page for a specific app
* @param appId The app identifier
* @param startPages User's start page preferences
* @returns The start page path (user preference or app default)
*/
export function getStartPage(appId: string, startPages: Record<string, string> = {}): string {
const config = APP_ROUTES[appId];
if (!config) {
return '/';
}
// Check if user has a preference for this app
const userPreference = startPages[appId];
if (userPreference) {
// Validate that the route is available
const isValid = config.availableRoutes.some((r) => r.path === userPreference);
if (isValid) {
return userPreference;
}
}
// Return app default
return config.defaultRoute;
}
/**
* Get available routes for a specific app
* @param appId The app identifier
* @returns Array of available routes or empty array if app not found
*/
export function getAvailableRoutes(appId: string): AppRoute[] {
return APP_ROUTES[appId]?.availableRoutes ?? [];
}
/**
* Get default route for a specific app
* @param appId The app identifier
* @returns The default route path or '/' if app not found
*/
export function getDefaultRoute(appId: string): string {
return APP_ROUTES[appId]?.defaultRoute ?? '/';
}

View file

@ -24,10 +24,14 @@ export type {
UserSettingsResponse,
UserSettingsStore,
UserSettingsStoreConfig,
// General Settings Types
StartPageConfig,
WeekStartDay,
GeneralSettings,
} from './types';
// User Settings Constants
export { DEFAULT_GLOBAL_SETTINGS } from './types';
export { DEFAULT_GLOBAL_SETTINGS, DEFAULT_GENERAL_SETTINGS } from './types';
// Constants
export {
@ -89,3 +93,7 @@ export {
loadA11yFromStorage,
saveA11yToStorage,
} from './a11y-utils';
// App Routes
export type { AppRoute, AppRouteConfig } from './app-routes';
export { APP_ROUTES, getStartPage, getAvailableRoutes, getDefaultRoute } from './app-routes';

View file

@ -223,6 +223,29 @@ export interface NavSettings {
sidebarCollapsed: boolean;
}
/**
* Start page configuration per app
* Keys are app IDs, values are route paths
*/
export type StartPageConfig = Record<string, string>;
/**
* Day of week for calendar/week starts
*/
export type WeekStartDay = 'monday' | 'sunday';
/**
* General settings (global preferences)
*/
export interface GeneralSettings {
/** Start page per app (e.g., { clock: '/stopwatch', calendar: '/week' }) */
startPages: StartPageConfig;
/** First day of week */
weekStartsOn: WeekStartDay;
/** Master toggle for all app sounds */
soundsEnabled: boolean;
}
/**
* Theme settings (synced to server)
*/
@ -240,6 +263,8 @@ export interface GlobalSettings {
nav: NavSettings;
theme: ThemeSettings;
locale: string;
/** General preferences (start pages, sounds, etc.) */
general: GeneralSettings;
}
/**
@ -258,6 +283,15 @@ export interface UserSettingsResponse {
appOverrides: Record<string, AppOverride>;
}
/**
* Default general settings
*/
export const DEFAULT_GENERAL_SETTINGS: GeneralSettings = {
startPages: {}, // Empty = use app defaults
weekStartsOn: 'monday',
soundsEnabled: true,
};
/**
* Default global settings
*/
@ -265,6 +299,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
nav: { desktopPosition: 'top', sidebarCollapsed: false },
theme: { mode: 'system', colorScheme: 'ocean' },
locale: 'de',
general: DEFAULT_GENERAL_SETTINGS,
};
/**
@ -277,6 +312,10 @@ export interface UserSettingsStore {
readonly theme: ThemeSettings;
/** Current locale */
readonly locale: string;
/** Resolved general settings */
readonly general: GeneralSettings;
/** Start page for current app (resolved from settings or default) */
readonly startPage: string;
/** Raw global settings */
readonly globalSettings: GlobalSettings;
/** Whether current app has an override */
@ -294,6 +333,10 @@ export interface UserSettingsStore {
updateAppOverride: (settings: AppOverride) => Promise<void>;
/** Remove app override (revert to global) */
removeAppOverride: () => Promise<void>;
/** Set start page for a specific app */
setStartPage: (appId: string, path: string) => Promise<void>;
/** Update general settings */
updateGeneral: (settings: Partial<GeneralSettings>) => Promise<void>;
}
/**

View file

@ -6,9 +6,11 @@ import type {
NavSettings,
ThemeSettings,
UserSettingsResponse,
GeneralSettings,
} from './types';
import { DEFAULT_GLOBAL_SETTINGS } from './types';
import { DEFAULT_GLOBAL_SETTINGS, DEFAULT_GENERAL_SETTINGS } from './types';
import { isBrowser } from './utils';
import { getStartPage as getStartPageFromConfig } from './app-routes';
const STORAGE_KEY_PREFIX = 'manacore-user-settings';
@ -66,6 +68,15 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
// Derived: whether this app has an override
const hasAppOverride = $derived(!!appOverrides[appId]);
// Derived: resolved general settings (always from global)
const general = $derived<GeneralSettings>({
...DEFAULT_GENERAL_SETTINGS,
...globalSettings.general,
});
// Derived: start page for current app
const startPage = $derived(getStartPageFromConfig(appId, general.startPages));
/**
* Save current settings to localStorage (for offline fallback)
*/
@ -172,6 +183,14 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
nav: { ...globalSettings.nav, ...settings.nav },
theme: { ...globalSettings.theme, ...settings.theme },
locale: settings.locale ?? globalSettings.locale,
general: {
...globalSettings.general,
...settings.general,
startPages: {
...globalSettings.general?.startPages,
...settings.general?.startPages,
},
},
};
saveToStorage();
@ -234,6 +253,35 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
}
}
/**
* Update start page for a specific app
*/
async function setStartPage(targetAppId: string, path: string): Promise<void> {
await updateGlobal({
general: {
startPages: {
[targetAppId]: path,
},
},
} as Partial<GlobalSettings>);
}
/**
* Update general settings
*/
async function updateGeneral(settings: Partial<GeneralSettings>): Promise<void> {
await updateGlobal({
general: {
...globalSettings.general,
...settings,
startPages: {
...globalSettings.general?.startPages,
...settings.startPages,
},
},
});
}
/**
* Remove app override (revert to global settings)
*/
@ -276,6 +324,12 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
get locale() {
return locale;
},
get general() {
return general;
},
get startPage() {
return startPage;
},
get globalSettings() {
return globalSettings;
},
@ -293,5 +347,7 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
updateGlobal,
updateAppOverride,
removeAppOverride,
setStartPage,
updateGeneral,
};
}