mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 20:36:42 +02:00
feat(shared-ui, manacore/web): cross-app navigation enhancement (3 phases)
Phase 1: Enhanced App Drawer in PillNavigation - appNavigationStore: localStorage-backed favorites, recents, usage counts - AppDrawer: replaces PillDropdown for apps with search, favorites, recents, grid - All apps using PillNavigation get this automatically Phase 2: Cmd+K Command Palette (GlobalSpotlight) - GlobalSpotlight: modal with app search, quick actions, keyboard navigation - useGlobalSpotlight: Cmd+K / Ctrl+K keyboard listener - Integrated into PillNavigation via optional spotlightActions prop Phase 3: Improved /home page - AppRow: horizontal scrollable app row for favorites/recents with pin toggle - ActivityFeed: cross-app timeline (completed tasks, upcoming events, contacts) - Replaced hardcoded 3-category layout with dynamic favorites, recents, activity feed, and usage-frequency sorted app grid Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4dfa2cc899
commit
ffd608cbb9
10 changed files with 1985 additions and 133 deletions
142
packages/shared-ui/src/navigation/appNavigationStore.svelte.ts
Normal file
142
packages/shared-ui/src/navigation/appNavigationStore.svelte.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* App Navigation Store
|
||||
*
|
||||
* Tracks favorite apps, recently visited apps, and usage counts.
|
||||
* Persists to localStorage for cross-session retention.
|
||||
* Pattern follows recentInputHistory.ts
|
||||
*/
|
||||
|
||||
const STORAGE_KEY_FAVORITES = 'mana-app-favorites';
|
||||
const STORAGE_KEY_RECENT = 'mana-app-recent';
|
||||
const STORAGE_KEY_USAGE = 'mana-app-usage-counts';
|
||||
const MAX_RECENT = 8;
|
||||
|
||||
export interface RecentAppEntry {
|
||||
id: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
// --- Standalone functions (non-reactive) ---
|
||||
|
||||
export function getFavoriteApps(): string[] {
|
||||
if (typeof window === 'undefined') return [];
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY_FAVORITES);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getRecentApps(): RecentAppEntry[] {
|
||||
if (typeof window === 'undefined') return [];
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY_RECENT);
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getUsageCounts(): Record<string, number> {
|
||||
if (typeof window === 'undefined') return {};
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY_USAGE);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleFavoriteApp(appId: string): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
const current = getFavoriteApps();
|
||||
const index = current.indexOf(appId);
|
||||
if (index >= 0) {
|
||||
current.splice(index, 1);
|
||||
} else {
|
||||
current.push(appId);
|
||||
}
|
||||
localStorage.setItem(STORAGE_KEY_FAVORITES, JSON.stringify(current));
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
export function recordAppVisit(appId: string): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
// Update recent apps
|
||||
const recent = getRecentApps();
|
||||
const filtered = recent.filter((r) => r.id !== appId);
|
||||
const updated = [{ id: appId, timestamp: Date.now() }, ...filtered].slice(0, MAX_RECENT);
|
||||
localStorage.setItem(STORAGE_KEY_RECENT, JSON.stringify(updated));
|
||||
|
||||
// Update usage counts
|
||||
const counts = getUsageCounts();
|
||||
counts[appId] = (counts[appId] || 0) + 1;
|
||||
localStorage.setItem(STORAGE_KEY_USAGE, JSON.stringify(counts));
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
export function clearRecentApps(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY_RECENT);
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
// --- Reactive Svelte 5 store ---
|
||||
|
||||
export function createAppNavigationStore() {
|
||||
let favorites = $state<string[]>(getFavoriteApps());
|
||||
let recentApps = $state<RecentAppEntry[]>(getRecentApps());
|
||||
let usageCounts = $state<Record<string, number>>(getUsageCounts());
|
||||
|
||||
function refresh() {
|
||||
favorites = getFavoriteApps();
|
||||
recentApps = getRecentApps();
|
||||
usageCounts = getUsageCounts();
|
||||
}
|
||||
|
||||
function toggleFavorite(appId: string) {
|
||||
toggleFavoriteApp(appId);
|
||||
refresh();
|
||||
}
|
||||
|
||||
function isFavorite(appId: string): boolean {
|
||||
return favorites.includes(appId);
|
||||
}
|
||||
|
||||
function visit(appId: string) {
|
||||
recordAppVisit(appId);
|
||||
refresh();
|
||||
}
|
||||
|
||||
function clearRecent() {
|
||||
clearRecentApps();
|
||||
refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
get favorites() {
|
||||
return favorites;
|
||||
},
|
||||
get recentApps() {
|
||||
return recentApps;
|
||||
},
|
||||
get usageCounts() {
|
||||
return usageCounts;
|
||||
},
|
||||
toggleFavorite,
|
||||
isFavorite,
|
||||
recordAppVisit: visit,
|
||||
clearRecent,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue