Fix lint, make design more consistent, add license texts,
All checks were successful
Queue Release Build / prepare (push) Successful in 11s
Deploy Web Apps / deploy (push) Successful in 14m0s
Queue Release Build / build-linux (push) Successful in 35m41s
Queue Release Build / build-windows (push) Successful in 28m53s
Queue Release Build / finalize (push) Successful in 2m6s

This commit is contained in:
2026-04-02 04:08:53 +02:00
parent 37cac95b38
commit ae0ee8fac7
45 changed files with 988 additions and 572 deletions

View File

@@ -1,18 +1,18 @@
<div class="theme-grid-editor rounded-2xl border border-border bg-background/50 p-3">
<div class="theme-grid-editor rounded-lg border border-border bg-card/40 p-3">
<div class="mb-3 flex items-center justify-between gap-3 px-1">
<div>
<p class="text-sm font-semibold text-foreground">{{ container().label }}</p>
<p class="text-xs text-muted-foreground">{{ container().description }}</p>
</div>
<div class="rounded-full bg-secondary px-2.5 py-1 text-[11px] font-medium text-muted-foreground">
<div class="rounded-md bg-secondary px-2.5 py-1 text-[11px] font-medium text-muted-foreground">
{{ container().columns }} cols x {{ container().rows }} rows
</div>
</div>
<div
#canvasRef
class="theme-grid-editor__frame relative overflow-hidden rounded-xl border border-border/80"
class="theme-grid-editor__frame relative overflow-hidden rounded-lg border border-border/80"
[ngStyle]="frameStyle()"
>
<div class="theme-grid-editor__grid"></div>
@@ -40,7 +40,7 @@
<p class="mt-1 line-clamp-2 text-[11px] leading-4 text-muted-foreground">{{ item.description }}</p>
</div>
<div class="rounded-full bg-background/80 px-2 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm">
<div class="rounded-md bg-background px-2 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm">
{{ item.grid.x }},{{ item.grid.y }} · {{ item.grid.w }}x{{ item.grid.h }}
</div>
</div>
@@ -57,7 +57,7 @@
@if (disabled()) {
<div
class="theme-grid-editor__disabled absolute inset-0 flex items-center justify-center rounded-xl bg-background/75 px-6 text-center text-sm text-muted-foreground backdrop-blur-sm"
class="theme-grid-editor__disabled absolute inset-0 flex items-center justify-center rounded-lg bg-background/75 px-6 text-center text-sm text-muted-foreground backdrop-blur-sm"
>
Fix JSON validation errors to re-enable the grid editor.
</div>

View File

@@ -5,8 +5,8 @@
.theme-grid-editor__frame {
aspect-ratio: 16 / 9;
background:
radial-gradient(circle at top, hsl(var(--primary) / 0.08), transparent 45%),
linear-gradient(180deg, hsl(var(--background) / 0.96), hsl(var(--card) / 0.98));
radial-gradient(circle at top, hsl(var(--primary) / 0.05), transparent 45%),
linear-gradient(180deg, hsl(var(--background) / 0.98), hsl(var(--card) / 0.98));
}
.theme-grid-editor__grid {
@@ -27,11 +27,9 @@
.theme-grid-editor__item-body {
height: 100%;
border: 1px solid hsl(var(--border) / 0.8);
border-radius: 1rem;
background:
linear-gradient(180deg, hsl(var(--card) / 0.96), hsl(var(--background) / 0.96)),
radial-gradient(circle at top right, hsl(var(--primary) / 0.1), transparent 45%);
box-shadow: 0 12px 30px rgb(0 0 0 / 10%);
border-radius: 0.5rem;
background: linear-gradient(180deg, hsl(var(--card) / 0.98), hsl(var(--background) / 0.98));
box-shadow: 0 1px 2px rgb(0 0 0 / 8%);
cursor: grab;
padding: 0.9rem;
}
@@ -42,9 +40,7 @@
.theme-grid-editor__item--selected .theme-grid-editor__item-body {
border-color: hsl(var(--primary));
box-shadow:
0 0 0 1px hsl(var(--primary)),
0 14px 34px hsl(var(--primary) / 0.18);
box-shadow: 0 0 0 1px hsl(var(--primary));
}
.theme-grid-editor__item--disabled .theme-grid-editor__item-body {
@@ -56,9 +52,7 @@
}
.theme-grid-editor__item:focus-visible .theme-grid-editor__item-body {
box-shadow:
0 0 0 2px hsl(var(--primary)),
0 14px 34px hsl(var(--primary) / 0.18);
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.25);
}
.theme-grid-editor__handle {
@@ -68,12 +62,12 @@
height: 0.95rem;
width: 0.95rem;
border: 0;
border-radius: 999px;
border-radius: 0.25rem;
background: hsl(var(--primary));
box-shadow: 0 0 0 3px hsl(var(--background));
box-shadow: 0 0 0 2px hsl(var(--background));
cursor: nwse-resize;
}
.theme-grid-editor__disabled {
border: 1px dashed hsl(var(--border));
}
}

View File

@@ -42,37 +42,14 @@ export class ThemeGridEditorComponent {
readonly itemChanged = output<{ key: string; grid: ThemeGridRect }>();
readonly itemSelected = output<string>();
private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
private dragState: DragState | null = null;
readonly canvasRef = viewChild.required<ElementRef<HTMLElement>>('canvasRef');
readonly frameStyle = computed(() => ({
'--theme-grid-columns': `${this.container().columns}`,
'--theme-grid-rows': `${this.container().rows}`
}));
itemStyle(item: ThemeGridEditorItem): Record<string, string> {
const { columns, rows } = this.container();
return {
left: `${(item.grid.x / columns) * 100}%`,
top: `${(item.grid.y / rows) * 100}%`,
width: `${(item.grid.w / columns) * 100}%`,
height: `${(item.grid.h / rows) * 100}%`
};
}
selectItem(key: string): void {
this.itemSelected.emit(key);
}
startMove(event: PointerEvent, item: ThemeGridEditorItem): void {
this.startDrag(event, item, 'move');
}
startResize(event: PointerEvent, item: ThemeGridEditorItem): void {
this.startDrag(event, item, 'resize');
}
private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
private dragState: DragState | null = null;
@HostListener('document:pointermove', ['$event'])
onPointerMove(event: PointerEvent): void {
@@ -112,6 +89,29 @@ export class ThemeGridEditorComponent {
this.dragState = null;
}
itemStyle(item: ThemeGridEditorItem): Record<string, string> {
const { columns, rows } = this.container();
return {
left: `${(item.grid.x / columns) * 100}%`,
top: `${(item.grid.y / rows) * 100}%`,
width: `${(item.grid.w / columns) * 100}%`,
height: `${(item.grid.h / rows) * 100}%`
};
}
selectItem(key: string): void {
this.itemSelected.emit(key);
}
startMove(event: PointerEvent, item: ThemeGridEditorItem): void {
this.startDrag(event, item, 'move');
}
startResize(event: PointerEvent, item: ThemeGridEditorItem): void {
this.startDrag(event, item, 'resize');
}
private startDrag(event: PointerEvent, item: ThemeGridEditorItem, mode: DragMode): void {
if (this.disabled()) {
return;

View File

@@ -0,0 +1,9 @@
<div
class="theme-json-code-editor"
[style.minHeight]="editorMinHeight()"
>
<div
#editorHostRef
class="theme-json-code-editor__host"
></div>
</div>

View File

@@ -0,0 +1,22 @@
:host {
display: block;
min-height: 0;
}
.theme-json-code-editor {
overflow: hidden;
border: 1px solid #334155;
border-radius: 0.5rem;
background: linear-gradient(180deg, #111827, #0f172a);
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.03);
}
.theme-json-code-editor:focus-within {
box-shadow:
inset 0 0 0 1px rgb(125 211 252 / 0.35),
0 0 0 1px rgb(56 189 248 / 0.25);
}
.theme-json-code-editor__host {
min-height: inherit;
}

View File

@@ -84,49 +84,10 @@ const THEME_JSON_EDITOR_THEME = EditorView.theme({
@Component({
selector: 'app-theme-json-code-editor',
standalone: true,
template: `
<div
class="theme-json-code-editor"
[style.minHeight]="editorMinHeight()"
>
<div
#editorHostRef
class="theme-json-code-editor__host"
></div>
</div>
`,
styles: `
:host {
display: block;
min-height: 0;
}
.theme-json-code-editor {
overflow: hidden;
border: 1px solid #2f405c;
border-radius: 1rem;
background:
radial-gradient(circle at top right, rgb(96 165 250 / 0.16), transparent 34%),
linear-gradient(180deg, #172033, #0e1625);
box-shadow:
inset 0 0 0 1px rgb(125 211 252 / 0.08),
0 0 0 1px rgb(15 23 42 / 0.16);
}
.theme-json-code-editor:focus-within {
box-shadow:
inset 0 0 0 1px rgb(125 211 252 / 0.5),
0 0 0 3px rgb(14 165 233 / 0.18);
}
.theme-json-code-editor__host {
min-height: inherit;
}
`
templateUrl: './theme-json-code-editor.component.html',
styleUrl: './theme-json-code-editor.component.scss'
})
export class ThemeJsonCodeEditorComponent implements OnDestroy {
private readonly zone = inject(NgZone);
readonly editorHostRef = viewChild<ElementRef<HTMLDivElement>>('editorHostRef');
readonly value = input.required<string>();
readonly fullscreen = input(false);
@@ -134,6 +95,8 @@ export class ThemeJsonCodeEditorComponent implements OnDestroy {
readonly editorMinHeight = computed(() => this.fullscreen() ? 'max(34rem, calc(100vh - 15rem))' : '28rem');
private readonly zone = inject(NgZone);
private editorView: EditorView | null = null;
private isApplyingExternalValue = false;

View File

@@ -1,5 +1,5 @@
<div
class="theme-settings flex min-h-0 w-full flex-col space-y-4"
class="theme-settings flex min-h-0 w-full flex-col space-y-3"
[class.min-h-full]="isFullscreen()"
[class.p-4]="isFullscreen()"
[class.theme-settings--fullscreen]="isFullscreen()"
@@ -15,21 +15,21 @@
<button
type="button"
(click)="startPicker()"
class="inline-flex items-center rounded-full border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary"
class="inline-flex items-center rounded-md border border-border bg-secondary px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Pick UI Element
</button>
<button
type="button"
(click)="formatDraft()"
class="inline-flex items-center rounded-full border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary"
class="inline-flex items-center rounded-md border border-border bg-secondary px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Format JSON
</button>
<button
type="button"
(click)="copyLlmThemeGuide()"
class="inline-flex items-center rounded-full border border-border bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary"
class="inline-flex items-center rounded-md border border-border bg-secondary px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Copy LLM Guide
</button>
@@ -37,14 +37,14 @@
type="button"
(click)="applyDraft()"
[disabled]="!draftIsValid()"
class="inline-flex items-center rounded-full bg-primary px-4 py-1.5 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-60"
class="inline-flex items-center rounded-md bg-primary px-4 py-1.5 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-60"
>
Apply Draft
</button>
<button
type="button"
(click)="restoreDefaultTheme()"
class="inline-flex items-center rounded-full border border-destructive/25 bg-destructive/10 px-3 py-1.5 text-sm font-medium text-destructive transition-colors hover:bg-destructive/15"
class="inline-flex items-center rounded-md border border-destructive/25 bg-destructive/10 px-3 py-1.5 text-sm font-medium text-destructive transition-colors hover:bg-destructive/15"
>
Restore Default
</button>
@@ -60,8 +60,14 @@
<div class="theme-settings__hero-grid mt-4">
<div class="theme-settings__hero-stat">
<div class="theme-settings__workspace-selector theme-settings__workspace-selector--compact">
<label class="theme-settings__workspace-selector-label">Workspace</label>
<label
for="theme-studio-workspace-select"
class="theme-settings__workspace-selector-label"
>
Workspace
</label>
<select
id="theme-studio-workspace-select"
class="theme-settings__workspace-select"
[value]="activeWorkspace()"
(change)="onWorkspaceSelect($event)"
@@ -89,13 +95,13 @@
</div>
@if (statusMessage()) {
<div class="mt-3 rounded-2xl border border-primary/20 bg-primary/10 px-4 py-3 text-sm text-primary">
<div class="mt-3 rounded-lg border border-primary/20 bg-primary/10 px-4 py-3 text-sm text-primary">
{{ statusMessage() }}
</div>
}
@if (!draftIsValid()) {
<div class="mt-3 rounded-2xl border border-destructive/30 bg-destructive/8 p-4">
<div class="mt-3 rounded-lg border border-destructive/30 bg-destructive/10 p-4">
<p class="text-sm font-semibold text-destructive">The draft is invalid. The last working theme is still active.</p>
<ul class="mt-2 space-y-1 text-sm text-destructive/90">
@for (error of draftErrors(); track error) {
@@ -122,7 +128,7 @@
type="button"
(click)="saveDraftAsNewTheme()"
[disabled]="!draftIsValid() || savedThemesBusy()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Save New
</button>
@@ -130,7 +136,7 @@
type="button"
(click)="saveDraftToSelectedTheme()"
[disabled]="!draftIsValid() || !selectedSavedTheme() || savedThemesBusy()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Save Selected
</button>
@@ -138,7 +144,7 @@
type="button"
(click)="useSelectedSavedTheme()"
[disabled]="!selectedSavedTheme()?.isValid || (savedThemesBusy() && savedThemes().length === 0)"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Use
</button>
@@ -146,7 +152,7 @@
type="button"
(click)="editSelectedSavedTheme()"
[disabled]="!selectedSavedTheme()?.isValid || (savedThemesBusy() && savedThemes().length === 0)"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Edit
</button>
@@ -154,7 +160,7 @@
type="button"
(click)="removeSelectedSavedTheme()"
[disabled]="!selectedSavedTheme() || (savedThemesBusy() && savedThemes().length === 0)"
class="rounded-full border border-destructive/25 bg-destructive/10 px-3 py-1.5 text-xs font-medium text-destructive transition-colors hover:bg-destructive/15 disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-destructive/25 bg-destructive/10 px-3 py-1.5 text-xs font-medium text-destructive transition-colors hover:bg-destructive/15 disabled:cursor-not-allowed disabled:opacity-60"
>
Remove
</button>
@@ -162,7 +168,7 @@
type="button"
(click)="refreshSavedThemes()"
[disabled]="savedThemesBusy() && savedThemes().length === 0"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Refresh
</button>
@@ -205,7 +211,7 @@
}
</div>
} @else {
<div class="mt-4 rounded-2xl border border-dashed border-border bg-background/60 px-4 py-5 text-sm text-muted-foreground">
<div class="mt-4 rounded-lg border border-dashed border-border bg-secondary/10 px-4 py-5 text-sm text-muted-foreground">
Save the current draft to create your first reusable Electron theme.
</div>
}
@@ -252,7 +258,7 @@
<span class="mt-2 block text-xs leading-5 text-muted-foreground">{{ entry.description }}</span>
</button>
} @empty {
<div class="rounded-2xl border border-dashed border-border bg-background/60 px-4 py-5 text-sm text-muted-foreground">
<div class="rounded-lg border border-dashed border-border bg-secondary/10 px-4 py-5 text-sm text-muted-foreground">
No registered theme keys match this filter.
</div>
}
@@ -283,7 +289,7 @@
<button
type="button"
(click)="jumpToStyles()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Open styles in JSON
</button>
@@ -291,7 +297,7 @@
<button
type="button"
(click)="jumpToLayout()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Open layout in JSON
</button>
@@ -315,10 +321,10 @@
<p class="text-sm font-semibold text-foreground">Theme JSON</p>
<div class="flex flex-wrap gap-2 text-[11px] font-medium text-muted-foreground">
<span class="rounded-full bg-secondary px-2.5 py-1">{{ draftLineCount() }} lines</span>
<span class="rounded-full bg-secondary px-2.5 py-1">{{ draftCharacterCount() }} chars</span>
<span class="rounded-full bg-secondary px-2.5 py-1">{{ draftErrorCount() }} errors</span>
<span class="rounded-full bg-slate-900 px-2.5 py-1 text-slate-200">IDE editor</span>
<span class="rounded-md bg-secondary px-2.5 py-1">{{ draftLineCount() }} lines</span>
<span class="rounded-md bg-secondary px-2.5 py-1">{{ draftCharacterCount() }} chars</span>
<span class="rounded-md bg-secondary px-2.5 py-1">{{ draftErrorCount() }} errors</span>
<span class="rounded-md bg-slate-900 px-2.5 py-1 text-slate-200">IDE editor</span>
</div>
</div>
@@ -334,7 +340,7 @@
}
@if (activeWorkspace() === 'inspector') {
<div class="space-y-6">
<div class="space-y-4">
<section class="theme-studio-card p-4">
<div class="flex items-center justify-between gap-3">
<p class="text-sm font-semibold text-foreground">Selection</p>
@@ -342,14 +348,14 @@
<button
type="button"
(click)="startPicker()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Pick live element
</button>
</div>
@if (selectedElement()) {
<div class="mt-4 rounded-2xl border border-border/80 bg-background/65 p-4">
<div class="mt-4 rounded-lg border border-border/80 bg-secondary/20 p-4">
<div class="flex items-center gap-2">
<p class="text-base font-semibold text-foreground">{{ selectedElement()!.label }}</p>
<span class="rounded-full bg-secondary px-2 py-0.5 font-mono text-[11px] text-muted-foreground">{{ selectedElement()!.key }}</span>
@@ -370,7 +376,7 @@
type="button"
(click)="addStarterAnimation()"
[disabled]="!draftIsValid()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Add fade animation
</button>
@@ -382,7 +388,7 @@
type="button"
(click)="applySuggestedProperty(field.key)"
[disabled]="!draftIsValid()"
class="rounded-2xl border border-border/80 bg-background/65 p-3 text-left transition-colors hover:bg-secondary/45 disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-lg border border-border/80 bg-secondary/20 p-3 text-left transition-colors hover:bg-secondary/40 disabled:cursor-not-allowed disabled:opacity-60"
>
<div class="flex items-start justify-between gap-3">
<div>
@@ -391,7 +397,7 @@
</div>
<span class="rounded-full bg-secondary px-2 py-0.5 text-[10px] font-medium text-muted-foreground">{{ field.type }}</span>
</div>
<div class="mt-3 inline-flex rounded-full bg-primary/10 px-2.5 py-1 font-mono text-[11px] text-primary">
<div class="mt-3 inline-flex rounded-md bg-primary/10 px-2.5 py-1 font-mono text-[11px] text-primary">
{{ field.example }}
</div>
</button>
@@ -399,7 +405,7 @@
</div>
</section>
<section class="theme-studio-card p-5">
<section class="theme-studio-card p-4">
<p class="text-sm font-semibold text-foreground">Animation Keys</p>
@if (animationKeys().length > 0) {
@@ -408,22 +414,22 @@
<button
type="button"
(click)="jumpToAnimation(animationKey)"
class="rounded-full border border-border bg-background px-3 py-1.5 font-mono text-[11px] text-foreground transition-colors hover:bg-secondary"
class="rounded-md border border-border bg-secondary px-3 py-1.5 font-mono text-[11px] text-foreground transition-colors hover:bg-secondary/80"
>
{{ animationKey }}
</button>
}
</div>
} @else {
<div class="mt-4 rounded-2xl border border-dashed border-border bg-background/60 px-4 py-5 text-sm text-muted-foreground">
<div class="mt-4 rounded-lg border border-dashed border-border bg-secondary/10 px-4 py-5 text-sm text-muted-foreground">
No custom animation keys yet.
</div>
}
<div class="mt-4 rounded-2xl border border-border/80 bg-background/65 p-4">
<div class="mt-4 rounded-lg border border-border/80 bg-secondary/20 p-4">
<div class="flex flex-wrap gap-2">
@for (field of THEME_ANIMATION_FIELDS; track field.key) {
<span class="rounded-full bg-secondary px-2.5 py-1 font-mono text-[11px] text-foreground/80">{{ field.key }}</span>
<span class="rounded-md bg-secondary px-2.5 py-1 font-mono text-[11px] text-foreground/80">{{ field.key }}</span>
}
</div>
</div>
@@ -441,7 +447,7 @@
<button
type="button"
(click)="selectContainer(container.key)"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80"
[class.bg-primary/10]="selectedContainer() === container.key"
[class.border-primary/40]="selectedContainer() === container.key"
>
@@ -453,7 +459,7 @@
type="button"
(click)="resetSelectedContainer()"
[disabled]="!draftIsValid()"
class="rounded-full border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
class="rounded-md border border-border bg-secondary px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary/80 disabled:cursor-not-allowed disabled:opacity-60"
>
Reset Container
</button>
@@ -473,19 +479,19 @@
@if (selectedElementGrid()) {
<div class="mt-4 grid gap-3 sm:grid-cols-4">
<div class="rounded-2xl border border-border/80 bg-background/65 p-3">
<div class="rounded-lg border border-border/80 bg-secondary/20 p-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">x</p>
<p class="mt-2 font-mono text-lg font-semibold text-foreground">{{ selectedElementGrid()!.grid.x }}</p>
</div>
<div class="rounded-2xl border border-border/80 bg-background/65 p-3">
<div class="rounded-lg border border-border/80 bg-secondary/20 p-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">y</p>
<p class="mt-2 font-mono text-lg font-semibold text-foreground">{{ selectedElementGrid()!.grid.y }}</p>
</div>
<div class="rounded-2xl border border-border/80 bg-background/65 p-3">
<div class="rounded-lg border border-border/80 bg-secondary/20 p-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">w</p>
<p class="mt-2 font-mono text-lg font-semibold text-foreground">{{ selectedElementGrid()!.grid.w }}</p>
</div>
<div class="rounded-2xl border border-border/80 bg-background/65 p-3">
<div class="rounded-lg border border-border/80 bg-secondary/20 p-3">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground">h</p>
<p class="mt-2 font-mono text-lg font-semibold text-foreground">{{ selectedElementGrid()!.grid.h }}</p>
</div>

View File

@@ -29,11 +29,11 @@
.theme-settings__workspace-select {
width: 100%;
border: 1px solid hsl(var(--border));
border-radius: 0.85rem;
background: hsl(var(--background) / 0.82);
padding: 0.65rem 0.8rem;
font-size: 0.88rem;
font-weight: 600;
border-radius: 0.5rem;
background: hsl(var(--secondary));
padding: 0.6rem 0.75rem;
font-size: 0.875rem;
font-weight: 500;
color: hsl(var(--foreground));
outline: none;
transition:
@@ -44,7 +44,7 @@
.theme-settings__workspace-select:focus {
border-color: hsl(var(--primary) / 0.4);
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.12);
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.2);
}
.theme-settings__workspace-selector--compact .theme-settings__workspace-select {
@@ -68,20 +68,19 @@
.theme-settings__saved-theme-list {
display: flex;
flex-direction: column;
gap: 0.7rem;
gap: 0.5rem;
}
.theme-settings__saved-theme-button {
width: 100%;
border: 1px solid hsl(var(--border) / 0.8);
border-radius: 1rem;
background: hsl(var(--background) / 0.65);
padding: 0.85rem;
border-radius: 0.5rem;
background: hsl(var(--secondary) / 0.25);
padding: 0.75rem;
text-align: left;
transition:
border-color 160ms ease,
background-color 160ms ease,
transform 160ms ease;
background-color 160ms ease;
}
.theme-settings__saved-theme-button:hover {
@@ -89,7 +88,7 @@
}
.theme-settings__saved-theme-button--active {
border-color: hsl(var(--primary) / 0.38);
border-color: hsl(var(--primary) / 0.35);
background: hsl(var(--primary) / 0.08);
}
@@ -114,4 +113,4 @@
display: block;
min-height: 0;
flex: 1 1 auto;
}
}

View File

@@ -44,12 +44,12 @@ type ThemeStudioWorkspace = 'editor' | 'inspector' | 'layout';
styleUrl: './theme-settings.component.scss'
})
export class ThemeSettingsComponent {
private readonly modal = inject(SettingsModalService);
private readonly theme = inject(ThemeService);
private readonly themeLibrary = inject(ThemeLibraryService);
private readonly registry = inject(ThemeRegistryService);
private readonly picker = inject(ElementPickerService);
private readonly layoutSync = inject(LayoutSyncService);
readonly modal = inject(SettingsModalService);
readonly theme = inject(ThemeService);
readonly themeLibrary = inject(ThemeLibraryService);
readonly registry = inject(ThemeRegistryService);
readonly picker = inject(ElementPickerService);
readonly layoutSync = inject(LayoutSyncService);
readonly editorRef = viewChild<ThemeJsonCodeEditorComponent>('jsonEditorRef');

View File

@@ -0,0 +1,29 @@
@if (picker.isPicking()) {
<div class="pointer-events-none fixed inset-x-0 bottom-4 z-[95] flex justify-center px-4">
<div class="pointer-events-auto max-w-xl rounded-lg border border-border bg-card px-4 py-3 shadow-lg backdrop-blur">
<div class="flex flex-wrap items-center gap-3">
<div class="min-w-0 flex-1">
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-primary">Theme Picker Active</p>
<p class="mt-1 text-sm text-foreground">Click a highlighted area to inspect its theme key.</p>
<p class="mt-1 text-xs text-muted-foreground">
Hovering:
<span class="font-medium text-foreground">{{ hoveredEntry()?.label || 'Move over a themeable region' }}</span>
@if (hoveredEntry()) {
<span class="ml-1 rounded-md bg-secondary px-2 py-0.5 font-mono text-[11px] text-foreground/80">
{{ hoveredEntry()!.key }}
</span>
}
</p>
</div>
<button
type="button"
(click)="cancel()"
class="inline-flex items-center rounded-md border border-border bg-secondary px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Cancel
</button>
</div>
</div>
</div>
}

View File

@@ -12,41 +12,11 @@ import { ThemeRegistryService } from '../application/theme-registry.service';
selector: 'app-theme-picker-overlay',
standalone: true,
imports: [CommonModule],
template: `
@if (picker.isPicking()) {
<div class="pointer-events-none fixed inset-x-0 bottom-4 z-[95] flex justify-center px-4">
<div class="pointer-events-auto max-w-xl rounded-2xl border border-border bg-card/95 px-4 py-3 shadow-2xl backdrop-blur-xl">
<div class="flex flex-wrap items-center gap-3">
<div class="min-w-0 flex-1">
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-primary">Theme Picker Active</p>
<p class="mt-1 text-sm text-foreground">
Click a highlighted area to inspect its theme key.
</p>
<p class="mt-1 text-xs text-muted-foreground">
Hovering:
<span class="font-medium text-foreground">{{ hoveredEntry()?.label || 'Move over a themeable region' }}</span>
@if (hoveredEntry()) {
<span class="ml-1 rounded-full bg-secondary px-2 py-0.5 font-mono text-[11px] text-foreground/80">{{ hoveredEntry()!.key }}</span>
}
</p>
</div>
<button
type="button"
(click)="cancel()"
class="inline-flex items-center rounded-full border border-border bg-secondary px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-secondary/80"
>
Cancel
</button>
</div>
</div>
</div>
}
`
templateUrl: './theme-picker-overlay.component.html'
})
export class ThemePickerOverlayComponent {
readonly picker = inject(ElementPickerService);
private readonly registry = inject(ThemeRegistryService);
readonly registry = inject(ThemeRegistryService);
readonly hoveredEntry = computed(() => {
return this.registry.getDefinition(this.picker.hoveredKey());