Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 41 additions & 6 deletions src/lib/components/ConfigEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
let initialPayload: any = null;
let rawMode = $state(false);
let rawJson = $state('');
let scriptTextarea = $state<HTMLTextAreaElement | null>(null);

let formData = $state({
name: '',
Expand Down Expand Up @@ -432,19 +433,26 @@
<!-- SCRIPT -->
<section class="col-full">
<p class="eyebrow"><span class="gt">&gt;</span> SCRIPT</p>
<div class="term">
<div class="term script-term">
<div class="term-head">
<span class="dot dot-red"></span><span class="dot dot-amber"></span><span class="dot dot-green"></span>
<span class="term-name">post-install.sh — runs after packages</span>
<span class="term-edit"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>editable</span>
<label class="term-name" for="post-install-script">post-install.sh</label>
<button type="button" class="term-edit" onclick={() => scriptTextarea?.focus()}><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>Edit script</button>
</div>
<textarea
id="post-install-script"
class="script-textarea"
bind:this={scriptTextarea}
bind:value={formData.custom_script}
aria-describedby="script-hint"
rows="10"
spellcheck="false"
autocomplete="off"
autocapitalize="off"
placeholder={scriptPlaceholder}
></textarea>
</div>
<p class="field-hint" id="script-hint">Edit the script above, then click Save changes. Runs after packages, shell, dotfiles, and macOS preferences.</p>
</section>
</div>
{/if}
Expand Down Expand Up @@ -858,21 +866,42 @@
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 0.68rem;
padding: 5px 8px;
border: 1px solid var(--border-hover);
border-radius: 6px;
background: var(--bg-tertiary);
font-family: inherit;
font-size: 0.72rem;
color: var(--accent);
cursor: pointer;
}

.term-edit:hover {
border-color: var(--accent);
}

.term-edit:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

.script-term:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent);
}

.script-textarea {
display: block;
width: 100%;
min-height: 148px;
min-height: 220px;
padding: 18px 20px;
background: transparent;
border: none;
outline: none;
resize: vertical;
font-family: inherit;
font-size: 0.8rem;
line-height: 1.9;
line-height: 1.7;
color: var(--text-secondary);
}

Expand Down Expand Up @@ -931,4 +960,10 @@
grid-template-columns: 1fr;
}
}

@media (max-width: 480px) {
.script-term .dot {
display: none;
}
}
</style>
133 changes: 99 additions & 34 deletions src/lib/components/MacOSPreferencesEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -54,39 +54,52 @@
</script>

<div class="prefs">
<p class="prefs-hint">{macosPrefs.length} configured. Expand a category to manage settings.</p>
{#each byCategory as group (group.cat)}
<div class="group">
<div class="group-label">{group.cat}</div>
{#each group.items as item (item.id)}
{@const on = isOn(item)}
<div class="row">
<div class="row-text">
<div class="row-name">{item.label}</div>
<div class="row-desc">{item.description}</div>
<details class="group" name="macos-preference-category">
<summary>
<span class="group-label">{group.cat}</span>
<span class="group-count">{group.items.filter(isOn).length} / {group.items.length} included</span>
<svg class="chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m9 5 7 7-7 7" /></svg>
</summary>
<div class="group-items">
{#each group.items as item (item.id)}
{@const on = isOn(item)}
<div class="row">
<div class="row-text">
<div class="row-name">{item.label}</div>
<div class="row-desc">{item.description}</div>
</div>
<button class="knob" class:on role="switch" aria-checked={on} aria-label={item.label} onclick={() => toggle(item)}>
<span class="knob-dot"></span>
</button>
</div>
<button class="knob" class:on role="switch" aria-checked={on} aria-label={item.label} onclick={() => toggle(item)}>
<span class="knob-dot"></span>
</button>
</div>
{/each}
</div>
{/each}
</div>
</details>
{/each}

{#if customPrefs.length > 0}
<div class="group">
<div class="group-label">Custom</div>
{#each customPrefs as pref (pref.domain + ':' + pref.key)}
<div class="row">
<div class="row-text">
<div class="row-name">{pref.desc || pref.key}</div>
<div class="row-desc">{pref.domain} {pref.key} = {pref.value}</div>
<details class="group" name="macos-preference-category">
<summary>
<span class="group-label">Custom</span>
<span class="group-count">{customPrefs.length} included</span>
<svg class="chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m9 5 7 7-7 7" /></svg>
</summary>
<div class="group-items">
{#each customPrefs as pref (pref.domain + ':' + pref.key)}
<div class="row">
<div class="row-text">
<div class="row-name">{pref.desc || pref.key}</div>
<div class="row-desc">{pref.domain} {pref.key} = {pref.value}</div>
</div>
<button class="knob on" role="switch" aria-checked="true" aria-label="Remove {pref.key}" onclick={() => removeCustom(pref)}>
<span class="knob-dot"></span>
</button>
</div>
<button class="knob on" role="switch" aria-checked="true" aria-label="Remove {pref.key}" onclick={() => removeCustom(pref)}>
<span class="knob-dot"></span>
</button>
</div>
{/each}
</div>
{/each}
</div>
</details>
{/if}
</div>

Expand All @@ -96,17 +109,68 @@
flex-direction: column;
}

.group + .group {
margin-top: 14px;
.prefs-hint {
font-size: 0.72rem;
color: var(--text-muted);
line-height: 1.5;
margin: 0 0 10px;
}

.group {
border-bottom: 1px solid var(--border);
}

summary {
display: flex;
align-items: center;
gap: 12px;
padding: 13px 4px;
cursor: pointer;
list-style: none;
border-radius: 5px;
}

summary::-webkit-details-marker {
display: none;
}

summary:hover {
background: var(--bg-tertiary);
}

summary:focus-visible,
.knob:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

.group-label {
font-size: 0.66rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
font-size: 0.8rem;
color: var(--text-primary);
}

.group-count {
margin-left: auto;
font-size: 0.68rem;
color: var(--text-muted);
padding: 8px 0 2px;
white-space: nowrap;
}

.chevron {
flex-shrink: 0;
color: var(--text-muted);
}

.group[open] .chevron {
transform: rotate(90deg);
}

.group-items {
max-height: 340px;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
padding: 0 4px 8px;
}

.row {
Expand All @@ -120,6 +184,7 @@

.row-text {
min-width: 0;
overflow-wrap: anywhere;
}

.row-name {
Expand Down
Loading