feat(themes): polish theme editor, updates, and card motion

- Lazy-load the theme editor, workshop, diagnostics, logs, and presence editor.
- Check installed themes for newer workshop versions and support updates with backup/restore.
- Preserve accordion state for the current editor session and add section icons.
- Animate installed and workshop cards on insertion, and fix the duplicate presence idle action.
This commit is contained in:
HorizonCode committed 2026-11-04 09:54:58 +01:00
1 parent 51f5fa3e46
commit d56ed9d786
16 files changed
+720 -434

No files matched your search

@@ -38,12 +38,15 @@
Film,
Gauge,
Image as ImageIcon,
Layers,
LayoutGrid,
Megaphone,
MousePointerClick,
Move,
Play,
RefreshCw,
Repeat,
Scan,
Shapes,
Sparkles,
Spline,
@@ -221,7 +224,7 @@
</div>
</div>
<ThemeSection title="Position & size" bordered={false} contentClass="space-y-3 pt-2">
<ThemeSection title="Position & size" icon={Move} bordered={false} contentClass="space-y-3 pt-2">
<div class="space-y-1">
<Label for="element-name" class={label}>Layer name</Label>
<Input
@@ -290,6 +293,7 @@
<ThemeSection
title="Layer state"
icon={Layers}
contentClass="flex flex-wrap items-center gap-x-4 gap-y-2 pt-2.5"
>
<div class="flex items-center gap-1.5">
@@ -402,7 +406,11 @@
/>
{#if element.type === 'text' || element.type === 'data'}
<ThemeSection title={element.type === 'data' ? 'Data' : 'Text'} contentClass="space-y-3 pt-2.5">
<ThemeSection
title={element.type === 'data' ? 'Data' : 'Text'}
icon={Type}
contentClass="space-y-3 pt-2.5"
>
{#if element.type === 'data'}
<div class="space-y-1.5">
<Label for="element-data-field" class={label}>Value</Label>
@@ -695,7 +703,7 @@
<ThemeTextStyleControls {element} onchange={(patch) => Object.assign(element, patch)} />
</ThemeSection>
{:else if element.type === 'svelte'}
<ThemeSection title="Svelte component" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Svelte component" icon={Component} contentClass="space-y-3 pt-2.5">
<div
class="rounded-md border border-amber-500/25 bg-amber-500/10 p-2 text-[9px] leading-relaxed text-amber-100"
>
@@ -721,7 +729,7 @@
</div>
</ThemeSection>
{:else if element.type === 'image' || element.type === 'video'}
<ThemeSection title="Media" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Media" icon={ImageIcon} contentClass="space-y-3 pt-2.5">
<div class="rounded-md border border-theme-800 bg-black/20 p-2">
<p class="text-[10px] uppercase tracking-wide text-muted-foreground">Asset</p>
<p class="mt-1 truncate font-mono text-[10px] text-theme-100">{element.src}</p>
@@ -829,7 +837,7 @@
{/if}
</ThemeSection>
{:else if element.type === 'lottie'}
<ThemeSection title="Lottie animation" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Lottie animation" icon={Film} contentClass="space-y-3 pt-2.5">
<div class="rounded-md border border-theme-800 bg-black/20 p-2">
<p class="text-[10px] uppercase tracking-wide text-muted-foreground">Animation</p>
<p class="mt-1 truncate font-mono text-[10px] text-theme-100">{element.src}</p>
@@ -885,7 +893,7 @@
</p>
</ThemeSection>
{:else if element.type === 'shape'}
<ThemeSection title="Shape" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Shape" icon={Shapes} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-shape" class={label}>Shape</Label>
<Select.Root
@@ -1153,7 +1161,7 @@
</div>
</ThemeSection>
{:else if element.type === 'icon'}
<ThemeSection title="Icon" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Icon" icon={Sparkles} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-icon" class={label}>Icon</Label>
<ThemeIconPicker bind:value={element.icon} {iconSet} />
@@ -1185,7 +1193,7 @@
</div>
</ThemeSection>
{:else if element.type === 'repeater'}
<ThemeSection title="Repeater" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Repeater" icon={Repeat} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-repeater-source" class={label}>Tile</Label>
<Select.Root
@@ -1307,7 +1315,7 @@
</p>
</ThemeSection>
{:else if element.type === 'gauge'}
<ThemeSection title="Gauge" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Gauge" icon={Gauge} contentClass="space-y-3 pt-2.5">
<div class="grid grid-cols-2 gap-2">
<div class="space-y-1">
<Label for="element-gauge-mode" class={label}>Style</Label>
@@ -1423,7 +1431,7 @@
</div>
</ThemeSection>
{:else if element.type === 'ticker'}
<ThemeSection title="Ticker" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Ticker" icon={Type} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-ticker-text" class={label}>Text</Label>
<Textarea
@@ -1520,7 +1528,7 @@
{/if}
</ThemeSection>
{:else if element.type === 'button'}
<ThemeSection title="Button" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Button" icon={MousePointerClick} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-button-label" class={label}>Label</Label>
<Input id="element-button-label" class={field} bind:value={element.label} />
@@ -1704,7 +1712,7 @@
</div>
</ThemeSection>
{:else if element.type === 'path'}
<ThemeSection title="Vector path" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Vector path" icon={Spline} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-path-data" class={label}>SVG path data</Label>
<Textarea
@@ -1850,7 +1858,7 @@
</div>
</ThemeSection>
{:else if element.type === 'particles'}
<ThemeSection title="Particles" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Particles" icon={Sparkles} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-particles-preset" class={label}>Preset</Label>
<Select.Root
@@ -1954,7 +1962,7 @@
</p>
</ThemeSection>
{:else if element.type === 'clock'}
<ThemeSection title="Clock" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Clock" icon={Clock} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-clock-mode" class={label}>Style</Label>
<Select.Root
@@ -2121,7 +2129,7 @@
</p>
</ThemeSection>
{:else if element.type === 'countdown'}
<ThemeSection title="Countdown" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Countdown" icon={Timer} contentClass="space-y-3 pt-2.5">
<div class="space-y-1.5">
<Label for="element-countdown-target" class={label}>Target date & time</Label>
<DatePicker
@@ -2220,7 +2228,7 @@
</div>
</ThemeSection>
{:else if element.type === 'widget'}
<ThemeSection title="Widget" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Widget" icon={LayoutGrid} contentClass="space-y-3 pt-2.5">
<div class="space-y-1.5">
<Label for="element-widget-type" class={label}>Widget Type</Label>
<Select.Root
@@ -2558,7 +2566,7 @@
</ThemeSection>
{/if}
<ThemeSection title="Mask" contentClass="space-y-3 pt-2.5">
<ThemeSection title="Mask" icon={Scan} contentClass="space-y-3 pt-2.5">
<div class="space-y-1">
<Label for="element-mask-shape" class={label}>Clip shape</Label>
<Select.Root