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:
1 parent
51f5fa3e46
commit
d56ed9d786
16 files changed
+720
-434
No files matched your search
@@ -951,11 +951,12 @@ export async function reviewCommunityTheme(
|
||||
*/
|
||||
export async function downloadAndInstallCommunityTheme(
|
||||
theme: CommunityTheme,
|
||||
onProgress?: (status: string, percentage: number) => void
|
||||
onProgress?: (status: string, percentage: number) => void,
|
||||
options?: { folderName?: string }
|
||||
): Promise<{ success: boolean; folderName: string }> {
|
||||
const { onProgress: reportProgress = () => {} } = { onProgress };
|
||||
const baseThemesDir = await themesDirectory();
|
||||
const folderName = theme.id;
|
||||
const folderName = options?.folderName?.trim() || theme.id;
|
||||
const targetFolder = await path.join(baseThemesDir, folderName);
|
||||
|
||||
reportProgress('Downloading theme package...', 10);
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
import { normalizeThemeTags } from '$lib/themeV2';
|
||||
import { reloadThemes, updateInstalledThemeTags, type Theme } from '$lib/themes';
|
||||
import { currentUser } from '$lib/userAuthentication';
|
||||
import { reduceAnimations } from '$lib/userSettings';
|
||||
import WorkshopTooltip from './WorkshopTooltip.svelte';
|
||||
import {
|
||||
Archive,
|
||||
@@ -87,6 +88,7 @@
|
||||
import { sileo } from 'sileo';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
let {
|
||||
onApplyTheme,
|
||||
@@ -2051,7 +2053,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-3 gap-3 pb-4">
|
||||
{#each themes as theme (theme.id)}
|
||||
{#each themes as theme, index (theme.id)}
|
||||
{@const localTheme =
|
||||
localThemeMap.get(theme.id.toLowerCase()) ||
|
||||
localThemeMap.get(theme.name.toLowerCase())}
|
||||
@@ -2077,6 +2079,9 @@
|
||||
class="group flex flex-col overflow-hidden rounded-xl border bg-theme-900/30 {isActive
|
||||
? 'border-primary/60 ring-1 ring-primary/20'
|
||||
: 'border-theme-800/60'} transition-all duration-300 hover:-translate-y-0.5 hover:border-white/20 hover:bg-theme-900/50 hover:shadow-xl hover:shadow-black/30"
|
||||
class:workshop-card-enter={!$reduceAnimations}
|
||||
style="animation-delay: {Math.min(index * 24, 144)}ms"
|
||||
out:fade={{ duration: $reduceAnimations ? 0 : 140 }}
|
||||
>
|
||||
<!-- 16:9 Thumbnail -->
|
||||
<div
|
||||
@@ -4481,3 +4486,20 @@
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
</Tooltip.Provider>
|
||||
|
||||
<style>
|
||||
.workshop-card-enter {
|
||||
animation: workshop-card-in 240ms cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||||
}
|
||||
|
||||
@keyframes workshop-card-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -110,6 +110,8 @@
|
||||
import { formatBytes, openURL } from '$lib/utils';
|
||||
import {
|
||||
AlertTriangle,
|
||||
AlignCenter,
|
||||
AlignHorizontalSpaceAround,
|
||||
Boxes,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
@@ -128,12 +130,14 @@
|
||||
Download,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileText,
|
||||
Film,
|
||||
Gauge,
|
||||
Globe,
|
||||
GripVertical,
|
||||
Hand,
|
||||
Image as ImageIcon,
|
||||
Info,
|
||||
Keyboard,
|
||||
Layers,
|
||||
LayoutGrid,
|
||||
@@ -5075,7 +5079,12 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="min-h-0 flex-1 space-y-3 overflow-y-auto p-3">
|
||||
<ThemeSection title="Theme details" bordered={false} contentClass="space-y-3 pt-2">
|
||||
<ThemeSection
|
||||
title="Theme details"
|
||||
icon={Info}
|
||||
bordered={false}
|
||||
contentClass="space-y-3 pt-2"
|
||||
>
|
||||
<div class="space-y-1.5">
|
||||
<Label for="theme-name" class={labelClass}>Name</Label>
|
||||
<Input
|
||||
@@ -5234,7 +5243,11 @@
|
||||
onchange={setVariableDefault}
|
||||
/>
|
||||
|
||||
<ThemeSection title="Launcher Layout & Chrome" contentClass="space-y-3 pt-2.5">
|
||||
<ThemeSection
|
||||
title="Launcher Layout & Chrome"
|
||||
icon={LayoutGrid}
|
||||
contentClass="space-y-3 pt-2.5"
|
||||
>
|
||||
<div class="space-y-1.5">
|
||||
<Label class={labelClass}>Layout Preset</Label>
|
||||
<div class="grid grid-cols-3 gap-1 rounded-md bg-black/30 p-1">
|
||||
@@ -5419,7 +5432,7 @@
|
||||
</div>
|
||||
</ThemeSection>
|
||||
|
||||
<ThemeSection title="Background" contentClass="space-y-3 pt-2.5">
|
||||
<ThemeSection title="Background" icon={ImageIcon} contentClass="space-y-3 pt-2.5">
|
||||
<div class="grid grid-cols-4 gap-1 rounded-md bg-black/30 p-1">
|
||||
{#each backgroundTypes as option (option.value)}
|
||||
<Button
|
||||
@@ -6230,6 +6243,7 @@
|
||||
<div class="min-h-0 flex-1 space-y-3 overflow-y-auto p-3">
|
||||
<ThemeSection
|
||||
title="Align"
|
||||
icon={AlignCenter}
|
||||
bordered={false}
|
||||
headingClass="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"
|
||||
contentClass="space-y-2 pt-2"
|
||||
@@ -6275,6 +6289,7 @@
|
||||
</ThemeSection>
|
||||
<ThemeSection
|
||||
title="Distribute"
|
||||
icon={AlignHorizontalSpaceAround}
|
||||
headingClass="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"
|
||||
contentClass="space-y-2 pt-2.5"
|
||||
>
|
||||
@@ -6298,6 +6313,7 @@
|
||||
</ThemeSection>
|
||||
<ThemeSection
|
||||
title="Layers"
|
||||
icon={Layers}
|
||||
headingClass="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"
|
||||
contentClass="space-y-2 pt-2.5"
|
||||
>
|
||||
@@ -6621,6 +6637,7 @@
|
||||
|
||||
<ThemeSection
|
||||
title="Theme summary"
|
||||
icon={FileText}
|
||||
class="mt-4"
|
||||
bordered={false}
|
||||
contentClass="space-y-2 pt-2"
|
||||
@@ -6649,6 +6666,7 @@
|
||||
|
||||
<ThemeSection
|
||||
title="Shortcuts"
|
||||
icon={Keyboard}
|
||||
class="mt-4"
|
||||
bordered={false}
|
||||
open={false}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import Slider from '$lib/components/ui/slider/slider.svelte';
|
||||
import type { ThemeV2Filters } from '$lib/themeV2';
|
||||
import { SlidersHorizontal } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
filters,
|
||||
@@ -67,6 +68,7 @@
|
||||
|
||||
<ThemeSection
|
||||
title="Filters"
|
||||
icon={SlidersHorizontal}
|
||||
open={false}
|
||||
actions={filterActions}
|
||||
contentClass="space-y-2.5 pt-2.5"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
type ThemeV2Group,
|
||||
type ThemeV2MaskShape,
|
||||
} from '$lib/themeV2';
|
||||
import { Boxes, RefreshCw, Ungroup } from '@lucide/svelte';
|
||||
import { Boxes, Layers, RefreshCw, Scan, Ungroup } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
group = $bindable(),
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ThemeSection title="Group" bordered={false} contentClass="space-y-3 pt-2">
|
||||
<ThemeSection title="Group" icon={Boxes} bordered={false} contentClass="space-y-3 pt-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="group-name" class={label}>Group name</Label>
|
||||
<Input
|
||||
@@ -96,6 +96,7 @@
|
||||
|
||||
<ThemeSection
|
||||
title="Group 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">
|
||||
@@ -167,7 +168,7 @@
|
||||
ondelete={ondeleterule}
|
||||
/>
|
||||
|
||||
<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="group-mask-shape" class={label}>Clip shape</Label>
|
||||
<Select.Root
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import ThemeBehaviorInspector from '$lib/components/theme/ThemeBehaviorInspector.svelte';
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import { themeEditorSectionOpen } from '$lib/themeEditorUiState';
|
||||
import { themeBehaviorSummary, themeGroupInteractiveCount } from '$lib/themeLogic';
|
||||
import type {
|
||||
ThemeV2Behavior,
|
||||
@@ -30,8 +31,13 @@
|
||||
ondelete: (id: string) => void;
|
||||
} = $props();
|
||||
|
||||
let open = $state(false);
|
||||
const sectionKey = 'Logic';
|
||||
let open = $state(themeEditorSectionOpen.get(sectionKey) ?? false);
|
||||
let selectedId = $state<string | undefined>(undefined);
|
||||
|
||||
$effect(() => {
|
||||
themeEditorSectionOpen.set(sectionKey, open);
|
||||
});
|
||||
let section = $state<HTMLElement>();
|
||||
let lastFocusToken = 0;
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { themeEditorSectionOpen } from '$lib/themeEditorUiState';
|
||||
import { ChevronDown } from '@lucide/svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import type { Component, Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
title,
|
||||
open = $bindable(true),
|
||||
open: initialOpen = true,
|
||||
icon: Icon,
|
||||
badge,
|
||||
description,
|
||||
@@ -27,6 +29,12 @@
|
||||
headingClass?: string;
|
||||
bordered?: boolean;
|
||||
} = $props();
|
||||
|
||||
let isOpen = $state(untrack(() => themeEditorSectionOpen.get(title) ?? initialOpen));
|
||||
|
||||
$effect(() => {
|
||||
themeEditorSectionOpen.set(title, isOpen);
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="{bordered ? 'border-t border-theme-800 pt-3 ' : ''}{className}">
|
||||
@@ -34,8 +42,8 @@
|
||||
<button
|
||||
type="button"
|
||||
class="group flex min-w-0 flex-1 items-center gap-1.5 rounded-md py-0.5 text-left transition-colors hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
|
||||
aria-expanded={open}
|
||||
onclick={() => (open = !open)}
|
||||
aria-expanded={isOpen}
|
||||
onclick={() => (isOpen = !isOpen)}
|
||||
>
|
||||
{#if Icon}
|
||||
<Icon class="size-3.5 shrink-0 text-primary" />
|
||||
@@ -49,7 +57,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
<ChevronDown
|
||||
class="ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform duration-200 {open
|
||||
class="ml-auto size-3.5 shrink-0 text-muted-foreground transition-transform duration-200 {isOpen
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
/>
|
||||
@@ -61,7 +69,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if open}
|
||||
{#if isOpen}
|
||||
<div class={contentClass}>
|
||||
{#if description}
|
||||
<p class="text-[10px] leading-relaxed text-muted-foreground">{description}</p>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import type { ThemeV2TextDecoration, ThemeV2TextStyle, ThemeV2TextTransform } from '$lib/themeV2';
|
||||
import { Bold, Italic, RotateCcw, Strikethrough, Underline } from '@lucide/svelte';
|
||||
import { Bold, Italic, RotateCcw, Strikethrough, Type, Underline } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
element,
|
||||
@@ -89,6 +89,7 @@
|
||||
|
||||
<ThemeSection
|
||||
title="Text style"
|
||||
icon={Type}
|
||||
open={false}
|
||||
actions={textStyleActions}
|
||||
contentClass="space-y-2 pt-2.5"
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
|
||||
<ThemeSection
|
||||
title="Transform"
|
||||
icon={RotateCw}
|
||||
open={false}
|
||||
actions={transformActions}
|
||||
contentClass="space-y-2 pt-2.5"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
import type { ThemeV2ScriptValue } from '$lib/themeV2';
|
||||
import { RotateCcw } from '@lucide/svelte';
|
||||
import { Braces, RotateCcw } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
variables,
|
||||
@@ -38,7 +38,12 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<ThemeSection title="Variables" badge={variables.length} contentClass="space-y-2 pt-2">
|
||||
<ThemeSection
|
||||
title="Variables"
|
||||
icon={Braces}
|
||||
badge={variables.length}
|
||||
contentClass="space-y-2 pt-2"
|
||||
>
|
||||
<p class="text-[10px] leading-relaxed text-muted-foreground">
|
||||
Starting values for
|
||||
<code class="rounded bg-theme-900 px-1 py-0.5 text-theme-100">{'{name}'}</code>
|
||||
|
||||
@@ -125,8 +125,8 @@ export const DISCORD_PRESENCE_PRESETS: DiscordPresencePreset[] = [
|
||||
},
|
||||
button2: {
|
||||
enabled: true,
|
||||
label: 'View Profile',
|
||||
url: '{profileUrl}',
|
||||
label: 'Join EZPPFarm',
|
||||
url: 'https://ez-pp.farm/discord',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
|
||||
/**
|
||||
* Open state for accordion sections in the current theme-editor WebView session.
|
||||
* This module is re-evaluated when the editor window closes, so the state is not
|
||||
* persisted across app launches.
|
||||
*/
|
||||
export const themeEditorSectionOpen = new SvelteMap<string, boolean>();
|
||||
+3
-2
@@ -522,6 +522,7 @@ export const reloadThemes = async () => {
|
||||
if (ACTIVE_STATUSES.has(storeTheme.status)) return storeTheme;
|
||||
const theme = installedMap.get(storeTheme.name);
|
||||
if (!theme) return storeTheme;
|
||||
const versionChanged = storeTheme.version !== theme.version;
|
||||
|
||||
// Replace the object rather than mutating it so the theme cards see the
|
||||
// new values when the list is rendered from a filtered derived array.
|
||||
@@ -538,8 +539,8 @@ export const reloadThemes = async () => {
|
||||
nsfw: theme.nsfw,
|
||||
workshopId: theme.workshopId,
|
||||
definition: theme.definition,
|
||||
updateAvailable: theme.updateAvailable,
|
||||
availableVersion: theme.availableVersion,
|
||||
updateAvailable: versionChanged ? false : storeTheme.updateAvailable,
|
||||
availableVersion: versionChanged ? undefined : storeTheme.availableVersion,
|
||||
cssStyle: theme.cssStyle,
|
||||
backups: theme.backups ?? storeTheme.backups,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import Logo from '$assets/logo.png';
|
||||
import DiscordPresenceEditor from '$lib/components/presence/DiscordPresenceEditor.svelte';
|
||||
import ThemeEditorWindow from '$lib/components/theme/ThemeEditorWindow.svelte';
|
||||
import ThemeV2Background from '$lib/components/theme/ThemeV2Background.svelte';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import AnimatedBg from '$lib/components/ui/animated-bg/AnimatedBg.svelte';
|
||||
@@ -449,9 +447,19 @@
|
||||
/>
|
||||
|
||||
{#if editorWindow}
|
||||
<ThemeEditorWindow />
|
||||
{#await import('$lib/components/theme/ThemeEditorWindow.svelte')}
|
||||
<div class="h-screen w-screen bg-theme-950"></div>
|
||||
{:then editorModule}
|
||||
{@const ThemeEditorWindow = editorModule.default}
|
||||
<ThemeEditorWindow />
|
||||
{/await}
|
||||
{:else if presenceEditorWindow}
|
||||
<DiscordPresenceEditor standalone />
|
||||
{#await import('$lib/components/presence/DiscordPresenceEditor.svelte')}
|
||||
<div class="h-screen w-screen bg-theme-950"></div>
|
||||
{:then presenceModule}
|
||||
{@const DiscordPresenceEditor = presenceModule.default}
|
||||
<DiscordPresenceEditor standalone />
|
||||
{/await}
|
||||
{:else}
|
||||
{#if editorOpen}
|
||||
<div
|
||||
|
||||
+587
-391
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user