Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138import { useCallback, useEffect, useMemo, useState, type ReactNode,} from 'react';import type { ActionSet, RegisteredAction } from './types';import type { ActionRegistry } from './registry';import type { HotkeyManager } from './hotkey-manager';import type { HotkeyStore } from './hotkeyStore';import { ActionContext } from './ActionContext';import { CommandPalette } from './CommandPalette';import { getRendererPlatform } from '../lib/platform';
interface ActionProviderProps { children: ReactNode; /** ActionSets to register — each adapts a component or subsystem. */ actionSets: ActionSet[]; /** The action registry (created and owned by AppShell). */ registry: ActionRegistry; /** The hotkey manager (created and owned by AppShell). */ hotkeyManager: HotkeyManager; /** The hotkey store (created and owned by AppShell). */ hotkeyStore: HotkeyStore;}
/** * Provides the action registry, hotkey manager, and command palette to the app. * * Accepts ActionSets and core services via dependency injection. * AppShell creates the registry and hotkey manager; ActionProvider only * registers/unregisters action sets and renders the command palette. */export function ActionProvider({ children, actionSets, registry, hotkeyManager, hotkeyStore,}: ActionProviderProps) { const isMac = getRendererPlatform() === 'darwin';
// Command palette open state const [paletteOpen, setPaletteOpen] = useState(false);
// Build the full list of action sets including the internal palette set const allActionSets = useMemo<ActionSet[]>( () => [ ...actionSets, { id: 'palette', getActions: () => [ { id: 'open', name: 'Open command palette', description: 'Search and execute commands', hotkeys: [{ modifiers: ['Mod'], key: 'p' }], execute: () => setPaletteOpen(true), }, ], }, ], [actionSets], );
/** * Register all actions from all sets and wire their effective hotkeys. */ const registerAll = useCallback(() => { for (const set of allActionSets) { for (const def of set.getActions()) { const registered = registry.register(set.id, def); for (const hotkey of hotkeyStore.getEffectiveHotkeys(registered)) { hotkeyManager.registerHotkey(registered.id, hotkey); } } } }, [allActionSets, registry, hotkeyManager, hotkeyStore]);
/** * Unregister all actions and clear their hotkeys. */ const unregisterAll = useCallback(() => { for (const set of allActionSets) { registry.unregisterAll(set.id); hotkeyManager.removeSourceHotkeys(set.id); } }, [allActionSets, registry, hotkeyManager]);
// Register on mount / when action sets change. // Re-register hotkeys when the user edits an override. useEffect(() => { registerAll(); return unregisterAll; }, [registerAll, unregisterAll]);
// Re-register hotkeys immediately when the user edits an override, // without waiting for a React state cycle. useEffect(() => { return hotkeyStore.onChange(() => { unregisterAll(); registerAll(); }); }, [hotkeyStore, registerAll, unregisterAll]);
const handlePaletteExecute = useCallback( (action: RegisteredAction) => { void registry.execute(action.id); }, [registry], );
const availableActions = useMemo( () => registry.getAvailable(), [registry, paletteOpen], );
const value = useMemo( () => ({ registry, hotkeyStore }), [registry, hotkeyStore], );
return ( <ActionContext.Provider value={value}> {children} <CommandPalette isOpen={paletteOpen} onClose={() => setPaletteOpen(false)} onExecute={handlePaletteExecute} actions={availableActions} hotkeyStore={hotkeyStore} isMac={isMac} /> </ActionContext.Provider> );}