Skip to content

Commit 235bb0b

Browse files
committed
Create a context aware menu bar
1 parent 61545a4 commit 235bb0b

1 file changed

Lines changed: 170 additions & 84 deletions

File tree

frontend/src/lib/components/layout/ActionBar.svelte

Lines changed: 170 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
undo,
2323
redo,
2424
pushRedo,
25-
pushState,
2625
pushUndo,
2726
} from '$lib/stores/history.svelte';
2827
import {getSettings} from '$lib/stores/settings.svelte';
29-
import {showToast} from '$lib/stores/ui.svelte';
28+
import {getActiveTab, setActiveTab, showToast} from '$lib/stores/ui.svelte';
29+
import {getApiKey, getTotalResults} from '$lib/stores/wallhaven.svelte';
3030
import SaveDialog from '$lib/components/blueprints/SaveDialog.svelte';
3131
3232
let showImportMenu = $state(false);
@@ -36,6 +36,11 @@
3636
let installToOmarchy = $state(false);
3737
let isOmarchy = $state(false);
3838
39+
let activeTab = $derived(getActiveTab());
40+
let wallpaperSelected = $derived(getWallpaperPath() !== '');
41+
let apiKeySet = $derived(getApiKey() !== '');
42+
let totalResults = $derived(getTotalResults());
43+
3944
const exportAppGroups = [
4045
{
4146
label: 'Editor themes',
@@ -94,6 +99,8 @@
9499
let undoEnabled = $derived(getCanUndo());
95100
let redoEnabled = $derived(getCanRedo());
96101
102+
// --- Editor actions ---
103+
97104
async function handleApply() {
98105
setIsApplying(true);
99106
try {
@@ -110,7 +117,7 @@
110117
appOverrides: getAppOverrides(),
111118
});
112119
if (result.success) {
113-
// Apply light/dark mode to the app UI only after successful theme apply
120+
// Only sync light/dark mode to UI after backend confirms apply
114121
if (getLightMode()) {
115122
document.documentElement.classList.add('light-mode');
116123
} else {
@@ -235,102 +242,181 @@
235242
}
236243
</script>
237244

238-
<footer
239-
class="bg-bg-secondary border-border flex h-10 shrink-0 items-center justify-between border-t px-3"
240-
>
241-
<!-- Left side: Export, Save, Import -->
242-
<div class="flex items-center gap-1">
245+
{#snippet goToEditor()}
246+
{#if wallpaperSelected}
243247
<button
244-
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
245-
onclick={async () => {
246-
showExportDialog = true;
247-
try {
248-
const {IsOmarchyInstalled} = await import(
249-
'../../../../wailsjs/go/main/App'
250-
);
251-
isOmarchy = await IsOmarchyInstalled();
252-
} catch {
253-
isOmarchy = false;
254-
}
255-
}}>Export</button
248+
class="bg-accent text-bg-primary hover:bg-accent-hover px-4 py-1.5 text-[11px] font-medium transition-colors duration-100"
249+
onclick={() => setActiveTab('editor')}>Go to Editor</button
256250
>
251+
{/if}
252+
{/snippet}
257253

258-
<button
259-
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
260-
onclick={() => (showSaveDialog = true)}>Save</button
261-
>
254+
<footer
255+
class="bg-bg-secondary border-border flex h-10 shrink-0 items-center justify-between border-t px-3"
256+
>
257+
{#if activeTab === 'editor'}
258+
<div class="flex items-center gap-1">
259+
<button
260+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
261+
onclick={async () => {
262+
showExportDialog = true;
263+
try {
264+
const {IsOmarchyInstalled} = await import(
265+
'../../../../wailsjs/go/main/App'
266+
);
267+
isOmarchy = await IsOmarchyInstalled();
268+
} catch {
269+
isOmarchy = false;
270+
}
271+
}}>Export</button
272+
>
262273

263-
<!-- Import dropdown -->
264-
<div class="relative">
265274
<button
266275
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
267-
onclick={() => (showImportMenu = !showImportMenu)}
268-
>Import</button
276+
onclick={() => (showSaveDialog = true)}>Save</button
269277
>
270278

271-
{#if showImportMenu}
272-
<!-- svelte-ignore a11y_no_static_element_interactions -->
273-
<div
274-
class="fixed inset-0 z-30"
275-
onclick={() => (showImportMenu = false)}
276-
></div>
277-
<div
278-
class="bg-bg-secondary border-border absolute bottom-full left-0 z-40 mb-1 min-w-[140px] border shadow-lg"
279+
<div class="relative">
280+
<button
281+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
282+
onclick={() => (showImportMenu = !showImportMenu)}
283+
>Import</button
279284
>
280-
<button
281-
class="text-fg-secondary hover:text-fg-primary hover:bg-bg-hover w-full px-3 py-1.5 text-left text-[11px] transition-colors"
282-
onclick={() => handleImport('base16')}
283-
>Base16 (.yaml)</button
284-
>
285-
<button
286-
class="text-fg-secondary hover:text-fg-primary hover:bg-bg-hover w-full px-3 py-1.5 text-left text-[11px] transition-colors"
287-
onclick={() => handleImport('toml')}
288-
>Colors (.toml)</button
289-
>
290-
<button
291-
class="text-fg-secondary hover:text-fg-primary hover:bg-bg-hover w-full px-3 py-1.5 text-left text-[11px] transition-colors"
292-
onclick={() => handleImport('blueprint')}
293-
>Blueprint (.json)</button
285+
286+
{#if showImportMenu}
287+
<!-- svelte-ignore a11y_no_static_element_interactions -->
288+
<div
289+
class="fixed inset-0 z-30"
290+
onclick={() => (showImportMenu = false)}
291+
></div>
292+
<div
293+
class="bg-bg-secondary border-border absolute bottom-full left-0 z-40 mb-1 min-w-[140px] border shadow-lg"
294294
>
295-
</div>
296-
{/if}
297-
</div>
295+
<button
296+
class="text-fg-secondary hover:text-fg-primary hover:bg-bg-hover w-full px-3 py-1.5 text-left text-[11px] transition-colors"
297+
onclick={() => handleImport('base16')}
298+
>Base16 (.yaml)</button
299+
>
300+
<button
301+
class="text-fg-secondary hover:text-fg-primary hover:bg-bg-hover w-full px-3 py-1.5 text-left text-[11px] transition-colors"
302+
onclick={() => handleImport('toml')}
303+
>Colors (.toml)</button
304+
>
305+
<button
306+
class="text-fg-secondary hover:text-fg-primary hover:bg-bg-hover w-full px-3 py-1.5 text-left text-[11px] transition-colors"
307+
onclick={() => handleImport('blueprint')}
308+
>Blueprint (.json)</button
309+
>
310+
</div>
311+
{/if}
312+
</div>
298313

299-
<div class="bg-border mx-1 h-4 w-px"></div>
314+
<div class="bg-border mx-1 h-4 w-px"></div>
300315

301-
<button
302-
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100 disabled:cursor-default disabled:opacity-25"
303-
onclick={handleUndo}
304-
disabled={!undoEnabled}>Undo</button
305-
>
306-
<button
307-
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100 disabled:cursor-default disabled:opacity-25"
308-
onclick={handleRedo}
309-
disabled={!redoEnabled}>Redo</button
310-
>
311-
</div>
316+
<button
317+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100 disabled:cursor-default disabled:opacity-25"
318+
onclick={handleUndo}
319+
disabled={!undoEnabled}>Undo</button
320+
>
321+
<button
322+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100 disabled:cursor-default disabled:opacity-25"
323+
onclick={handleRedo}
324+
disabled={!redoEnabled}>Redo</button
325+
>
326+
</div>
312327

313-
<!-- Right side: Clear, Reset, Apply -->
314-
<div class="flex items-center gap-1">
315-
<button
316-
class="text-destructive/60 hover:text-destructive hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
317-
onclick={handleClear}>Clear</button
318-
>
319-
<button
320-
class="text-destructive/60 hover:text-destructive hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
321-
onclick={handleReset}>Reset</button
322-
>
328+
<div class="flex items-center gap-1">
329+
<button
330+
class="text-destructive/60 hover:text-destructive hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
331+
onclick={handleClear}>Clear</button
332+
>
333+
<button
334+
class="text-destructive/60 hover:text-destructive hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
335+
onclick={handleReset}>Reset</button
336+
>
323337

324-
<div class="bg-border mx-1 h-4 w-px"></div>
338+
<div class="bg-border mx-1 h-4 w-px"></div>
325339

326-
<button
327-
class="bg-accent text-bg-primary hover:bg-accent-hover px-4 py-1.5 text-[11px] font-medium transition-colors duration-100 disabled:opacity-50"
328-
onclick={handleApply}
329-
disabled={getIsApplying()}
330-
>
331-
{getIsApplying() ? 'Applying...' : 'Apply Theme'}
332-
</button>
333-
</div>
340+
<button
341+
class="bg-accent text-bg-primary hover:bg-accent-hover px-4 py-1.5 text-[11px] font-medium transition-colors duration-100 disabled:opacity-50"
342+
onclick={handleApply}
343+
disabled={getIsApplying()}
344+
>
345+
{getIsApplying() ? 'Applying...' : 'Apply Theme'}
346+
</button>
347+
</div>
348+
{:else if activeTab === 'wallhaven'}
349+
<div class="flex items-center gap-2">
350+
{#if apiKeySet}
351+
<span class="text-success text-[11px]">API key set</span>
352+
{:else}
353+
<span class="text-fg-dimmed text-[11px]"
354+
>No API key (set in filters)</span
355+
>
356+
{/if}
357+
{#if totalResults > 0}
358+
<div class="bg-border mx-1 h-4 w-px"></div>
359+
<span class="text-fg-dimmed text-[11px]"
360+
>{totalResults.toLocaleString()} results</span
361+
>
362+
{/if}
363+
</div>
364+
<div class="flex items-center gap-1">
365+
{@render goToEditor()}
366+
</div>
367+
{:else if activeTab === 'local'}
368+
<div class="flex items-center gap-1">
369+
<button
370+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
371+
onclick={async () => {
372+
try {
373+
const {ScanLocalWallpapers} = await import(
374+
'../../../../wailsjs/go/main/App'
375+
);
376+
await ScanLocalWallpapers();
377+
showToast('Wallpapers rescanned');
378+
// Force LocalBrowser to remount so it re-reads the file list
379+
setActiveTab('editor');
380+
setTimeout(() => setActiveTab('local'), 0);
381+
} catch {
382+
showToast('Failed to rescan');
383+
}
384+
}}>Rescan</button
385+
>
386+
</div>
387+
<div class="flex items-center gap-1">
388+
{@render goToEditor()}
389+
</div>
390+
{:else if activeTab === 'favorites'}
391+
<div class="flex items-center gap-1">
392+
<span class="text-fg-dimmed text-[11px]">Favorited wallpapers</span>
393+
</div>
394+
<div class="flex items-center gap-1">
395+
{@render goToEditor()}
396+
</div>
397+
{:else if activeTab === 'blueprints'}
398+
<div class="flex items-center gap-1">
399+
<button
400+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
401+
onclick={() => (showSaveDialog = true)}>Save Current</button
402+
>
403+
<button
404+
class="text-fg-dimmed hover:text-fg-secondary hover:bg-bg-hover px-2 py-1 text-[11px] transition-colors duration-100"
405+
onclick={() => handleImport('blueprint')}
406+
>Import Blueprint</button
407+
>
408+
</div>
409+
<div class="flex items-center gap-1">
410+
{@render goToEditor()}
411+
</div>
412+
{:else if activeTab === 'system'}
413+
<div class="flex items-center gap-1">
414+
<span class="text-fg-dimmed text-[11px]">System themes</span>
415+
</div>
416+
<div class="flex items-center gap-1">
417+
{@render goToEditor()}
418+
</div>
419+
{/if}
334420
</footer>
335421

336422
<!-- Export Dialog -->

0 commit comments

Comments
 (0)