Skip to content

Commit 6e458de

Browse files
committed
Add command palette (Ctrl+P) for discoverable actions
Modal with search input, keyboard navigation (arrows / Home / End / Enter), and subsequence fuzzy match so 'rzm' finds 'reset zoom'. Ships with 18 commands across Navigate, Theme, Edit, Wallpaper, and View categories; wallpaper-specific commands are hidden when no wallpaper is loaded. Extracts zoom helpers to lib/utils/zoom.ts and the apply/undo/redo/ extract/change-wallpaper handlers to lib/actions/themeActions.ts so the shortcut registrations and palette entries call the same implementations. Moves keymap and image-editor open/close flags into the ui store so commands can drive them.
1 parent 88931fd commit 6e458de

8 files changed

Lines changed: 573 additions & 113 deletions

File tree

frontend/src/App.svelte

Lines changed: 49 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -22,44 +22,47 @@
2222
getColorPickerOverrideRole,
2323
getEyedropperActive,
2424
setEyedropperActive,
25+
getCommandPaletteOpen,
26+
openCommandPalette,
27+
closeCommandPalette,
28+
getKeymapOpen,
29+
setKeymapOpen,
30+
toggleKeymap,
2531
} from '$lib/stores/ui.svelte';
2632
import {
27-
getIsApplying,
28-
setIsApplying,
2933
setWallpaperPath,
30-
getPalette,
31-
getWallpaperPath,
32-
getLightMode,
33-
getAdditionalImages,
34-
getExtendedColors,
35-
getAppOverrides,
36-
getAdjustments,
37-
setAdjustments,
3834
setPalette,
39-
setAdjustedExtendedColors,
35+
setAdjustments,
4036
setColor,
4137
setExtendedColor,
4238
setAppOverride,
4339
setLightMode,
4440
setExtractionMode,
4541
} from '$lib/stores/theme.svelte';
46-
import {getSettings} from '$lib/stores/settings.svelte';
42+
import {pushState} from '$lib/stores/history.svelte';
4743
import {
48-
undo as historyUndo,
49-
redo as historyRedo,
50-
pushRedo,
51-
pushState,
52-
pushUndo,
53-
} from '$lib/stores/history.svelte';
44+
applyTheme,
45+
undoAction,
46+
redoAction,
47+
} from '$lib/actions/themeActions';
48+
import {
49+
zoomIn,
50+
zoomOut,
51+
resetZoom,
52+
setZoom,
53+
getZoom,
54+
} from '$lib/utils/zoom';
5455
import Toast from '$lib/components/shared/Toast.svelte';
5556
import AboutStrip from '$lib/components/layout/AboutStrip.svelte';
5657
import KeymapDialog from '$lib/components/shared/KeymapDialog.svelte';
58+
import CommandPalette from '$lib/components/shared/CommandPalette.svelte';
5759
import {initKeyboardShortcuts, registerShortcut} from '$lib/utils/keyboard';
5860
import {hexToRgb} from '$lib/utils/color';
61+
import {buildCommands} from '$lib/commands/commands.svelte';
5962
import type {main} from '../wailsjs/go/models';
6063
61-
let showKeymap = $state(false);
6264
let activeTab = $derived(getActiveTab());
65+
let commands = $derived(buildCommands());
6366
let widgetMode = $state(false);
6467
let sliderWidget = $state(false);
6568
let themesSlider = $state(false);
@@ -93,50 +96,9 @@
9396
9497
initKeyboardShortcuts();
9598
96-
// Ctrl+Z - Undo
97-
registerShortcut('ctrl+z', () => {
98-
const snapshot = historyUndo();
99-
if (snapshot) {
100-
pushRedo(getPalette(), getExtendedColors(), getAdjustments());
101-
setPalette(snapshot.palette, true);
102-
setAdjustedExtendedColors(snapshot.extendedColors);
103-
setAdjustments(snapshot.adjustments);
104-
}
105-
});
106-
107-
// Ctrl+Shift+Z - Redo
108-
registerShortcut('ctrl+shift+z', () => {
109-
const snapshot = historyRedo();
110-
if (snapshot) {
111-
pushUndo(getPalette(), getExtendedColors(), getAdjustments());
112-
setPalette(snapshot.palette, true);
113-
setAdjustedExtendedColors(snapshot.extendedColors);
114-
setAdjustments(snapshot.adjustments);
115-
}
116-
});
117-
118-
// Ctrl+Enter - Apply theme
119-
registerShortcut('ctrl+enter', async () => {
120-
if (getIsApplying()) return;
121-
setIsApplying(true);
122-
try {
123-
const {ApplyTheme} = await import('../wailsjs/go/main/App');
124-
const result = await ApplyTheme({
125-
palette: getPalette(),
126-
wallpaperPath: getWallpaperPath(),
127-
lightMode: getLightMode(),
128-
additionalImages: getAdditionalImages(),
129-
extendedColors: getExtendedColors(),
130-
settings: getSettings(),
131-
appOverrides: getAppOverrides(),
132-
} as unknown as main.ApplyThemeRequest);
133-
if (result.success) showToast('Theme applied');
134-
} catch {
135-
showToast('Failed to apply theme');
136-
} finally {
137-
setIsApplying(false);
138-
}
139-
});
99+
registerShortcut('ctrl+z', undoAction);
100+
registerShortcut('ctrl+shift+z', redoAction);
101+
registerShortcut('ctrl+enter', applyTheme);
140102
141103
// Ctrl+S - Save blueprint
142104
registerShortcut('ctrl+s', () => {
@@ -185,56 +147,28 @@
185147
});
186148
});
187149
188-
// Ctrl+= / Ctrl+- / Ctrl+0 - Zoom controls
189-
const ZOOM_STEP = 0.1;
190-
const ZOOM_MIN = 0.5;
191-
const ZOOM_MAX = 2.0;
192-
const ZOOM_DEFAULT = 1.0;
193-
194-
function getZoom(): number {
195-
return parseFloat(localStorage.getItem('aether-zoom') || '1');
196-
}
197-
198-
function applyZoom(level: number) {
199-
const clamped = Math.min(
200-
ZOOM_MAX,
201-
Math.max(ZOOM_MIN, Math.round(level * 100) / 100)
202-
);
203-
document.documentElement.style.zoom = String(clamped);
204-
localStorage.setItem('aether-zoom', String(clamped));
205-
}
206-
207-
// Restore saved zoom level
208-
applyZoom(getZoom());
209-
210-
// Ctrl++ (on standard keyboards, + is Shift+=, so e.key is "+")
211-
registerShortcut('ctrl+shift++', () => {
212-
applyZoom(getZoom() + ZOOM_STEP);
213-
});
214-
215-
// Ctrl++ via numpad (no shift needed)
216-
registerShortcut('ctrl++', () => {
217-
applyZoom(getZoom() + ZOOM_STEP);
218-
});
219-
220-
registerShortcut('ctrl+-', () => {
221-
applyZoom(getZoom() - ZOOM_STEP);
222-
});
223-
224-
registerShortcut('ctrl+0', () => {
225-
applyZoom(ZOOM_DEFAULT);
226-
});
150+
setZoom(getZoom());
151+
registerShortcut('ctrl+shift++', zoomIn);
152+
registerShortcut('ctrl++', zoomIn);
153+
registerShortcut('ctrl+-', zoomOut);
154+
registerShortcut('ctrl+0', resetZoom);
227155
228156
// Ctrl+K - Show keymap
229157
registerShortcut('ctrl+k', () => {
230-
showKeymap = !showKeymap;
158+
toggleKeymap();
159+
});
160+
161+
// Ctrl+P - Command palette
162+
registerShortcut('ctrl+p', () => {
163+
if (getCommandPaletteOpen()) closeCommandPalette();
164+
else openCommandPalette();
231165
});
232166
233-
// Escape - Close modals
234167
registerShortcut('escape', () => {
235-
if (getEyedropperActive()) setEyedropperActive(false);
168+
if (getCommandPaletteOpen()) closeCommandPalette();
169+
else if (getEyedropperActive()) setEyedropperActive(false);
236170
else if (getColorPickerOpen()) closeColorPicker();
237-
else if (showKeymap) showKeymap = false;
171+
else if (getKeymapOpen()) setKeymapOpen(false);
238172
});
239173
240174
// Listen for events from Go
@@ -407,6 +341,14 @@
407341
<ActionBar />
408342
<AboutStrip />
409343
<Toast />
410-
<KeymapDialog open={showKeymap} onclose={() => (showKeymap = false)} />
344+
<KeymapDialog
345+
open={getKeymapOpen()}
346+
onclose={() => setKeymapOpen(false)}
347+
/>
348+
<CommandPalette
349+
open={getCommandPaletteOpen()}
350+
{commands}
351+
onclose={closeCommandPalette}
352+
/>
411353
</div>
412354
{/if}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type {main} from '../../../wailsjs/go/models';
2+
import {showToast} from '$lib/stores/ui.svelte';
3+
import {
4+
getIsApplying,
5+
setIsApplying,
6+
getIsExtracting,
7+
setIsExtracting,
8+
getWallpaperPath,
9+
setWallpaperPath,
10+
getPalette,
11+
setPalette,
12+
getLightMode,
13+
getAdditionalImages,
14+
getExtendedColors,
15+
getAppOverrides,
16+
getAdjustments,
17+
setAdjustments,
18+
setAdjustedExtendedColors,
19+
getExtractionMode,
20+
} from '$lib/stores/theme.svelte';
21+
import {getSettings} from '$lib/stores/settings.svelte';
22+
import {
23+
undo as historyUndo,
24+
redo as historyRedo,
25+
pushRedo,
26+
pushUndo,
27+
} from '$lib/stores/history.svelte';
28+
import {DEFAULT_ADJUSTMENTS} from '$lib/types/theme';
29+
30+
export async function applyTheme(): Promise<void> {
31+
if (getIsApplying()) return;
32+
setIsApplying(true);
33+
try {
34+
const {ApplyTheme} = await import('../../../wailsjs/go/main/App');
35+
const result = await ApplyTheme({
36+
palette: getPalette(),
37+
wallpaperPath: getWallpaperPath(),
38+
lightMode: getLightMode(),
39+
additionalImages: getAdditionalImages(),
40+
extendedColors: getExtendedColors(),
41+
settings: getSettings(),
42+
appOverrides: getAppOverrides(),
43+
} as unknown as main.ApplyThemeRequest);
44+
if (result.success) showToast('Theme applied');
45+
} catch {
46+
showToast('Failed to apply theme');
47+
} finally {
48+
setIsApplying(false);
49+
}
50+
}
51+
52+
export function undoAction(): void {
53+
const snapshot = historyUndo();
54+
if (!snapshot) return;
55+
pushRedo(getPalette(), getExtendedColors(), getAdjustments());
56+
setPalette(snapshot.palette, true);
57+
setAdjustedExtendedColors(snapshot.extendedColors);
58+
setAdjustments(snapshot.adjustments);
59+
}
60+
61+
export function redoAction(): void {
62+
const snapshot = historyRedo();
63+
if (!snapshot) return;
64+
pushUndo(getPalette(), getExtendedColors(), getAdjustments());
65+
setPalette(snapshot.palette, true);
66+
setAdjustedExtendedColors(snapshot.extendedColors);
67+
setAdjustments(snapshot.adjustments);
68+
}
69+
70+
export async function changeWallpaper(): Promise<void> {
71+
try {
72+
const {OpenFileDialog} = await import('../../../wailsjs/go/main/App');
73+
const path = await OpenFileDialog();
74+
if (path) {
75+
setWallpaperPath(path);
76+
showToast('Wallpaper changed — click Extract to generate palette');
77+
}
78+
} catch {
79+
showToast('Failed to change wallpaper');
80+
}
81+
}
82+
83+
export async function extractColors(): Promise<void> {
84+
const path = getWallpaperPath();
85+
if (!path || getIsExtracting()) return;
86+
setIsExtracting(true);
87+
try {
88+
const {ExtractColors} = await import('../../../wailsjs/go/main/App');
89+
const colors = await ExtractColors(
90+
path,
91+
getLightMode(),
92+
getExtractionMode()
93+
);
94+
setAdjustments({...DEFAULT_ADJUSTMENTS});
95+
setPalette(colors);
96+
showToast('Colors extracted');
97+
} catch {
98+
showToast('Failed to extract colors');
99+
} finally {
100+
setIsExtracting(false);
101+
}
102+
}

0 commit comments

Comments
 (0)