Add theme color editor and multi-step undo
- New /edit/theme-settings page with native color pickers for the background/primary/positive/negative theme colors plus light mode, contrast and saturation multipliers, custom-css-file path, and picker-disable. Pickers use hex; the backend converts to Glance's HSL "H S L" form before writing. Theme presets and full custom CSS intentionally still YAML. - Numbered backup chain: save() rotates up to 10 prior versions as glance.yml.bak.1 .. .10 instead of a single .bak. The /edit page lists them with humanized timestamps and a per-row Restore button. Single-step "Restore previous (toggle)" is preserved. - Restoring any backup saves the just-current state as a fresh .bak.1, so undo-the-undo always works. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e108feb74b
commit
7a0a4b9780
@@ -0,0 +1,80 @@
|
||||
{{- template "document.html" . }}
|
||||
|
||||
{{- define "document-title" }}Theme settings - {{ .App.Config.Branding.AppName }}{{ end }}
|
||||
|
||||
{{- define "document-body" }}
|
||||
<div class="flex flex-column body-content">
|
||||
<main class="content-bounds" style="padding-block: 2rem;">
|
||||
<div class="margin-bottom-15">
|
||||
<a class="color-subdue" href="{{ .App.Config.Server.BaseURL }}/edit">← Edit</a>
|
||||
</div>
|
||||
<h1 class="size-h1 margin-bottom-15">Theme</h1>
|
||||
<p class="color-subdue margin-bottom-25">
|
||||
Colors are stored as HSL in the YAML, but the picker below shows hex
|
||||
for convenience — Glance converts on save. Theme presets and custom CSS
|
||||
beyond the file path still need YAML.
|
||||
</p>
|
||||
|
||||
<form method="post" action="{{ .App.Config.Server.BaseURL }}/edit/api/theme-settings"
|
||||
style="display:flex;flex-direction:column;gap:1rem;max-width:36rem;">
|
||||
|
||||
<fieldset style="border:1px solid var(--color-widget-content-border);border-radius:4px;padding:1rem;">
|
||||
<legend class="color-subdue size-h5">Colors</legend>
|
||||
<div style="display:grid;grid-template-columns:1fr auto;gap:0.75rem;align-items:center;">
|
||||
<label for="bg">Background</label>
|
||||
<input id="bg" type="color" name="background-color" value="{{ if .ThemeSettings.BackgroundColorHex }}{{ .ThemeSettings.BackgroundColorHex }}{{ else }}#151823{{ end }}">
|
||||
|
||||
<label for="pc">Primary (accent)</label>
|
||||
<input id="pc" type="color" name="primary-color" value="{{ if .ThemeSettings.PrimaryColorHex }}{{ .ThemeSettings.PrimaryColorHex }}{{ else }}#e4cf8d{{ end }}">
|
||||
|
||||
<label for="ps">Positive</label>
|
||||
<input id="ps" type="color" name="positive-color" value="{{ if .ThemeSettings.PositiveColorHex }}{{ .ThemeSettings.PositiveColorHex }}{{ else }}#7fbf7f{{ end }}">
|
||||
|
||||
<label for="ng">Negative</label>
|
||||
<input id="ng" type="color" name="negative-color" value="{{ if .ThemeSettings.NegativeColorHex }}{{ .ThemeSettings.NegativeColorHex }}{{ else }}#e07f7f{{ end }}">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset style="border:1px solid var(--color-widget-content-border);border-radius:4px;padding:1rem;">
|
||||
<legend class="color-subdue size-h5">Tweaks</legend>
|
||||
|
||||
<label class="form-label widget-header" style="display:flex;flex-direction:row;align-items:center;gap:0.5rem;margin-bottom:0.75rem;">
|
||||
<input type="checkbox" name="light" {{ if .ThemeSettings.Light }}checked{{ end }}>
|
||||
<span>Light mode (use light text/background scheme)</span>
|
||||
</label>
|
||||
|
||||
<label class="form-label widget-header" style="display:flex;flex-direction:row;align-items:center;gap:0.5rem;margin-bottom:0.75rem;">
|
||||
<input type="checkbox" name="disable-picker" {{ if .ThemeSettings.DisablePicker }}checked{{ end }}>
|
||||
<span>Hide the theme picker in the header</span>
|
||||
</label>
|
||||
|
||||
<label class="form-label widget-header">
|
||||
Contrast multiplier
|
||||
<input type="number" step="0.05" min="0.5" max="2.0" name="contrast-multiplier" class="input"
|
||||
value="{{ if .ThemeSettings.ContrastMultiplier }}{{ .ThemeSettings.ContrastMultiplier }}{{ end }}" placeholder="1.0">
|
||||
<small class="color-subdue size-h5">Higher = stronger contrast for body text. Default 1.0.</small>
|
||||
</label>
|
||||
|
||||
<label class="form-label widget-header" style="margin-top:0.75rem;">
|
||||
Text saturation multiplier
|
||||
<input type="number" step="0.05" min="0" max="2.0" name="text-saturation-multiplier" class="input"
|
||||
value="{{ if .ThemeSettings.TextSaturationMultiplier }}{{ .ThemeSettings.TextSaturationMultiplier }}{{ end }}" placeholder="1.0">
|
||||
<small class="color-subdue size-h5">0 = monochrome text. Default 1.0.</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label class="form-label widget-header">
|
||||
Custom CSS file
|
||||
<input type="text" name="custom-css-file" class="input" value="{{ .ThemeSettings.CustomCSSFile }}" placeholder="/assets/custom.css">
|
||||
<small class="color-subdue size-h5">Path to a .css file under your assets-path, loaded after the bundled styles.</small>
|
||||
</label>
|
||||
|
||||
<div class="flex gap-10 margin-top-10">
|
||||
<button type="submit">Save</button>
|
||||
<a class="color-subdue" style="align-self:center;" href="{{ .App.Config.Server.BaseURL }}/edit">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
{{ template "footer.html" . }}
|
||||
</div>
|
||||
{{- end }}
|
||||
@@ -7,6 +7,9 @@
|
||||
<main class="content-bounds" style="padding-block: 2rem;">
|
||||
<div class="flex items-center gap-10">
|
||||
<h1 class="size-h1 grow">Edit</h1>
|
||||
<a href="{{ .App.Config.Server.BaseURL }}/edit/theme-settings">
|
||||
<button type="button">Theme</button>
|
||||
</a>
|
||||
<a href="{{ .App.Config.Server.BaseURL }}/edit/site-settings">
|
||||
<button type="button">Site settings</button>
|
||||
</a>
|
||||
@@ -50,15 +53,33 @@
|
||||
</form>
|
||||
|
||||
<hr style="margin-block: 2rem; border: none; border-top: 1px solid var(--color-widget-content-border);">
|
||||
<h3 class="size-h3 margin-bottom-10">Undo last save</h3>
|
||||
<h3 class="size-h3 margin-bottom-10">Saved versions</h3>
|
||||
<p class="color-subdue margin-bottom-10">
|
||||
Restores the previous contents of <code>{{ .App.ConfigPath }}</code> from the
|
||||
<code>.bak</code> file written on each save. Click again to redo.
|
||||
Up to {{ len .Backups }} previous versions of <code>{{ .App.ConfigPath }}</code> are kept.
|
||||
Restoring any one writes it as the current config and saves the just-current state as the new <em>most recent</em>, so you can always step back.
|
||||
</p>
|
||||
|
||||
<form method="post" action="{{ .App.Config.Server.BaseURL }}/edit/api/restore"
|
||||
onsubmit="return confirm('Restore the previous version of your config?');">
|
||||
<button type="submit">Restore previous version</button>
|
||||
onsubmit="return confirm('Restore the most recent backup? You can click again to flip back.');" class="margin-bottom-15">
|
||||
<button type="submit">Restore previous version (toggle)</button>
|
||||
</form>
|
||||
|
||||
<ul class="list list-gap-10">
|
||||
{{- range .Backups }}
|
||||
{{- if .Present }}
|
||||
<li class="flex items-center gap-10">
|
||||
<div class="grow">
|
||||
<span class="color-text-base">Version {{ .N }}</span>
|
||||
<span class="color-subdue size-h5"> — {{ .Time }} · {{ .SizeKB }} KB</span>
|
||||
</div>
|
||||
<form method="post" action="{{ $.App.Config.Server.BaseURL }}/edit/api/restore/{{ .N }}"
|
||||
onsubmit="return confirm('Restore this version? Current state will be saved as the new most recent backup.');">
|
||||
<button type="submit">Restore this</button>
|
||||
</form>
|
||||
</li>
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
</ul>
|
||||
</main>
|
||||
{{ template "footer.html" . }}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user