refactor(electrobun): centralize all shared state into global stores
New stores: - ui-store.svelte.ts: settingsOpen, paletteOpen, searchOpen, notifDrawerOpen, showWizard, settingsCategory, projectToDelete, showAddGroup, newGroupName - project-tabs-store.svelte.ts: per-project activeTab + activatedTabs via Map Wired: - App.svelte: 8 inline $state removed, reads/writes via ui-store - ProjectCard: activeTab/activatedTabs from project-tabs-store - SettingsDrawer: activeCategory from ui-store - CommandPalette: 4 commands call ui-store directly (no CustomEvent dispatch) Components are now pure view layers reading from stores.
This commit is contained in:
parent
c88577a34a
commit
2b1194c809
6 changed files with 230 additions and 60 deletions
|
|
@ -27,17 +27,20 @@
|
|||
import {
|
||||
getNotifications, clearAll as clearNotifications, getNotifCount,
|
||||
} from './notifications-store.svelte.ts';
|
||||
import {
|
||||
getSettingsOpen, setSettingsOpen, toggleSettings,
|
||||
getPaletteOpen, setPaletteOpen, togglePalette,
|
||||
getSearchOpen, toggleSearch,
|
||||
getNotifDrawerOpen, setNotifDrawerOpen, toggleNotifDrawer,
|
||||
getShowWizard, setShowWizard,
|
||||
getProjectToDelete, setProjectToDelete,
|
||||
getShowAddGroup, setShowAddGroup, toggleAddGroup,
|
||||
getNewGroupName, setNewGroupName, resetAddGroupForm,
|
||||
toggleWizard,
|
||||
} from './ui-store.svelte.ts';
|
||||
|
||||
// ── Local UI state (view-only, not domain state) ────────────
|
||||
// ── Local view-only state (not shared across components) ────
|
||||
let appReady = $state(false);
|
||||
let settingsOpen = $state(false);
|
||||
let paletteOpen = $state(false);
|
||||
let drawerOpen = $state(false);
|
||||
let searchOpen = $state(false);
|
||||
let showWizard = $state(false);
|
||||
let projectToDelete = $state<string | null>(null);
|
||||
let showAddGroup = $state(false);
|
||||
let newGroupName = $state('');
|
||||
let sessionStart = $state(Date.now());
|
||||
|
||||
// ── Blink timer ─────────────────────────────────────────────
|
||||
|
|
@ -81,28 +84,28 @@
|
|||
// ── Wizard handler ──────────────────────────────────────────
|
||||
function handleWizardCreated(result: WizardResult) {
|
||||
addProjectFromWizard(result);
|
||||
showWizard = false;
|
||||
setShowWizard(false);
|
||||
}
|
||||
|
||||
async function confirmDeleteProject() {
|
||||
if (!projectToDelete) return;
|
||||
await deleteProject(projectToDelete);
|
||||
projectToDelete = null;
|
||||
const toDelete = getProjectToDelete();
|
||||
if (!toDelete) return;
|
||||
await deleteProject(toDelete);
|
||||
setProjectToDelete(null);
|
||||
}
|
||||
|
||||
// ── Group add (local UI + store) ────────────────────────────
|
||||
async function handleAddGroup() {
|
||||
const name = newGroupName.trim();
|
||||
const name = getNewGroupName().trim();
|
||||
if (!name) return;
|
||||
await wsAddGroup(name);
|
||||
showAddGroup = false;
|
||||
newGroupName = '';
|
||||
resetAddGroupForm();
|
||||
}
|
||||
|
||||
// ── Notification drawer ─────────────────────────────────────
|
||||
function handleClearNotifications() {
|
||||
clearNotifications();
|
||||
drawerOpen = false;
|
||||
setNotifDrawerOpen(false);
|
||||
}
|
||||
|
||||
// ── Toast ref for agent notifications ───────────────────────
|
||||
|
|
@ -183,8 +186,8 @@
|
|||
trackAllProjects();
|
||||
});
|
||||
|
||||
keybindingStore.on('palette', () => { paletteOpen = !paletteOpen; });
|
||||
keybindingStore.on('settings', () => { settingsOpen = !settingsOpen; });
|
||||
keybindingStore.on('palette', () => { togglePalette(); });
|
||||
keybindingStore.on('settings', () => { toggleSettings(); });
|
||||
keybindingStore.on('group1', () => setActiveGroup(getGroups()[0]?.id));
|
||||
keybindingStore.on('group2', () => setActiveGroup(getGroups()[1]?.id));
|
||||
keybindingStore.on('group3', () => setActiveGroup(getGroups()[2]?.id));
|
||||
|
|
@ -194,7 +197,7 @@
|
|||
function handleSearchShortcut(e: KeyboardEvent) {
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
|
||||
e.preventDefault();
|
||||
searchOpen = !searchOpen;
|
||||
toggleSearch();
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', handleSearchShortcut);
|
||||
|
|
@ -202,10 +205,10 @@
|
|||
function handlePaletteCommand(e: Event) {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
switch (detail) {
|
||||
case 'settings': settingsOpen = !settingsOpen; break;
|
||||
case 'search': searchOpen = !searchOpen; break;
|
||||
case 'new-project': showWizard = true; break;
|
||||
case 'toggle-sidebar': settingsOpen = !settingsOpen; break;
|
||||
case 'settings': toggleSettings(); break;
|
||||
case 'search': toggleSearch(); break;
|
||||
case 'new-project': setShowWizard(true); break;
|
||||
case 'toggle-sidebar': toggleSettings(); break;
|
||||
default: console.log(`[palette] unhandled command: ${detail}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -221,15 +224,15 @@
|
|||
</script>
|
||||
|
||||
<SplashScreen ready={appReady} />
|
||||
<SettingsDrawer open={settingsOpen} onClose={() => settingsOpen = false} />
|
||||
<CommandPalette open={paletteOpen} onClose={() => paletteOpen = false} />
|
||||
<SearchOverlay open={searchOpen} onClose={() => searchOpen = false} />
|
||||
<SettingsDrawer open={getSettingsOpen()} onClose={() => setSettingsOpen(false)} />
|
||||
<CommandPalette open={getPaletteOpen()} onClose={() => setPaletteOpen(false)} />
|
||||
<SearchOverlay open={getSearchOpen()} onClose={() => setSearchOpen(false)} />
|
||||
<ToastContainer bind:this={toastRef} />
|
||||
<NotifDrawer
|
||||
open={drawerOpen}
|
||||
open={getNotifDrawerOpen()}
|
||||
notifications={getNotifications()}
|
||||
onClear={handleClearNotifications}
|
||||
onClose={() => drawerOpen = false}
|
||||
onClose={() => setNotifDrawerOpen(false)}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
|
@ -262,7 +265,7 @@
|
|||
<!-- Add group button -->
|
||||
<button
|
||||
class="group-btn add-group-btn"
|
||||
onclick={() => showAddGroup = !showAddGroup}
|
||||
onclick={() => toggleAddGroup()}
|
||||
aria-label="Add group"
|
||||
title="Add group"
|
||||
>
|
||||
|
|
@ -270,14 +273,15 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{#if showAddGroup}
|
||||
{#if getShowAddGroup()}
|
||||
<div class="add-group-form">
|
||||
<input
|
||||
class="add-group-input"
|
||||
type="text"
|
||||
placeholder="Group name"
|
||||
bind:value={newGroupName}
|
||||
onkeydown={(e) => { if (e.key === 'Enter') handleAddGroup(); if (e.key === 'Escape') showAddGroup = false; }}
|
||||
value={getNewGroupName()}
|
||||
oninput={(e) => setNewGroupName((e.target as HTMLInputElement).value)}
|
||||
onkeydown={(e) => { if (e.key === 'Enter') handleAddGroup(); if (e.key === 'Escape') setShowAddGroup(false); }}
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -286,7 +290,7 @@
|
|||
<!-- Add project button -->
|
||||
<button
|
||||
class="sidebar-icon"
|
||||
onclick={() => showWizard = !showWizard}
|
||||
onclick={() => toggleWizard()}
|
||||
aria-label="Add project"
|
||||
title="Add project"
|
||||
>
|
||||
|
|
@ -300,8 +304,8 @@
|
|||
<!-- Settings gear -->
|
||||
<button
|
||||
class="sidebar-icon"
|
||||
class:active={settingsOpen}
|
||||
onclick={() => settingsOpen = !settingsOpen}
|
||||
class:active={getSettingsOpen()}
|
||||
onclick={() => toggleSettings()}
|
||||
aria-label="Settings (Ctrl+,)"
|
||||
title="Settings (Ctrl+,)"
|
||||
data-testid="settings-btn"
|
||||
|
|
@ -347,9 +351,9 @@
|
|||
</div>
|
||||
|
||||
<!-- Project wizard overlay (display toggle) -->
|
||||
<div style:display={showWizard ? 'contents' : 'none'}>
|
||||
<div style:display={getShowWizard() ? 'contents' : 'none'}>
|
||||
<ProjectWizard
|
||||
onClose={() => showWizard = false}
|
||||
onClose={() => setShowWizard(false)}
|
||||
onCreated={handleWizardCreated}
|
||||
groupId={getActiveGroupId()}
|
||||
groups={getGroups().map(g => ({ id: g.id, name: g.name }))}
|
||||
|
|
@ -358,11 +362,11 @@
|
|||
</div>
|
||||
|
||||
<!-- Delete project confirmation -->
|
||||
{#if projectToDelete}
|
||||
{#if getProjectToDelete()}
|
||||
<div class="delete-overlay" role="listitem">
|
||||
<p class="delete-text">Delete project "{getProjects().find(p => p.id === projectToDelete)?.name}"?</p>
|
||||
<p class="delete-text">Delete project "{getProjects().find(p => p.id === getProjectToDelete())?.name}"?</p>
|
||||
<div class="add-card-actions">
|
||||
<button class="add-cancel" onclick={() => projectToDelete = null}>Cancel</button>
|
||||
<button class="add-cancel" onclick={() => setProjectToDelete(null)}>Cancel</button>
|
||||
<button class="delete-confirm" onclick={confirmDeleteProject}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -382,8 +386,8 @@
|
|||
|
||||
<button
|
||||
class="right-icon notif-btn"
|
||||
class:active={drawerOpen}
|
||||
onclick={() => drawerOpen = !drawerOpen}
|
||||
class:active={getNotifDrawerOpen()}
|
||||
onclick={() => toggleNotifDrawer()}
|
||||
aria-label="{getNotifCount() > 0 ? `${getNotifCount()} notifications` : 'Notifications'}"
|
||||
title="Notifications"
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue