1package systemviews
2
3import (
4 "github.com/bnema/dumber/internal/application/dto"
5 "github.com/bnema/dumber/internal/application/port"
6)
7
8templ ConfigView(data configRenderData) {
9 @Alert("success", data.Notice)
10 @Alert("error", data.Error)
11 @Section("sv-config-appearance", "Appearance") {
12 @AppearanceForm(data.Config)
13 }
14 @Section("sv-config-search", "Search") {
15 @SearchSettings(data.Config)
16 }
17 @Section("sv-config-performance", "Performance") {
18 @PerformanceSettings(data.Config)
19 }
20 @Section("sv-config-keybindings", "Keybindings") {
21 @Meta(configKeybindingSummary(data.Keybindings))
22 @ConfigKeybindings(data.Keybindings)
23 }
24 @Section("sv-config-values", "Current payload") {
25 @KV(flattenValues(data.Config))
26 }
27}
28
29templ AppearanceForm(config dto.SystemviewConfigPayload) {
30 <form class="sv-card-form sv-config-form" data-sv-action="config.appearance.save">
31 <label><span>Sans font</span><input name="sans_font" type="text" value={ config.Appearance.SansFont } required/></label>
32 <label><span>Serif font</span><input name="serif_font" type="text" value={ config.Appearance.SerifFont } required/></label>
33 <label><span>Monospace font</span><input name="monospace_font" type="text" value={ config.Appearance.MonospaceFont } required/></label>
34 <label><span>GTK UI font</span><input name="gtk_font" type="text" value={ config.Appearance.GtkFont } required/></label>
35 <label><span>Default font size</span><input name="default_font_size" type="number" min="1" max="72" value={ formatConfigInt(config.Appearance.DefaultFontSize) } required/></label>
36 <label><span>UI scale</span><input name="default_ui_scale" type="number" min="0.5" max="3" step="0.05" value={ formatConfigFloat(config.DefaultUIScale) } required/></label>
37 <label><span>Color scheme</span><select name="color_scheme">
38 <option value="default" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "default") }>System default</option>
39 <option value="prefer-light" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "prefer-light") }>Prefer light</option>
40 <option value="prefer-dark" selected?={ colorSchemeSelected(config.Appearance.ColorScheme, "prefer-dark") }>Prefer dark</option>
41 </select></label>
42 <fieldset class="sv-fieldset">
43 <legend>External theme</legend>
44 <p class="sv-meta">Provider noctalia supports colors-json (default Noctalia colors.json) and dumber-json (advanced user template).</p>
45 <label><span>Enable external theme</span><input name="external_theme_enabled" type="checkbox" checked?={ config.Appearance.ExternalTheme.Enabled }/></label>
46 <label><span>Provider</span><input name="external_theme_provider" type="text" value={ config.Appearance.ExternalTheme.Provider } placeholder="noctalia"/></label>
47 <label><span>Format</span><input name="external_theme_format" type="text" value={ config.Appearance.ExternalTheme.Format } placeholder="colors-json"/></label>
48 <label><span>Path</span><input name="external_theme_path" type="text" value={ config.Appearance.ExternalTheme.Path } placeholder="~/.config/noctalia/colors.json"/></label>
49 </fieldset>
50 @PaletteFields("Light palette", "light", config.Appearance.LightPalette)
51 @PaletteFields("Dark palette", "dark", config.Appearance.DarkPalette)
52 <div class="sv-button-row sv-form-actions">
53 <button class="sv-button" type="submit">@ButtonIconLabel(iconSave, "Save appearance")</button>
54 <button class="sv-button sv-button-secondary" type="button" data-sv-action="config.appearance.reset" data-sv-confirm="Reset appearance settings to defaults?">@ButtonIconLabel(iconRotateCCW, "Reset appearance")</button>
55 </div>
56 </form>
57}
58
59templ PaletteFields(title string, prefix string, palette dto.ColorPalette) {
60 <fieldset class="sv-fieldset">
61 <legend>{ title }</legend>
62 <label><span>Background</span><input name={ prefix + "_background" } type="text" value={ palette.Background } required/></label>
63 <label><span>Surface</span><input name={ prefix + "_surface" } type="text" value={ palette.Surface } required/></label>
64 <label><span>Surface variant</span><input name={ prefix + "_surface_variant" } type="text" value={ palette.SurfaceVariant } required/></label>
65 <label><span>Text</span><input name={ prefix + "_text" } type="text" value={ palette.Text } required/></label>
66 <label><span>Muted</span><input name={ prefix + "_muted" } type="text" value={ palette.Muted } required/></label>
67 <label><span>Accent</span><input name={ prefix + "_accent" } type="text" value={ palette.Accent } required/></label>
68 <label><span>Border</span><input name={ prefix + "_border" } type="text" value={ palette.Border } required/></label>
69 </fieldset>
70}
71
72templ SearchSettings(config dto.SystemviewConfigPayload) {
73 <form class="sv-card-form" data-sv-action="config.search.save">
74 <label><span>Default search engine</span><input name="default_search_engine" type="text" value={ config.DefaultSearchEngine } placeholder="https://duckduckgo.com/?q=%s" required/></label>
75 <button class="sv-button" type="submit">@ButtonIconLabel(iconSave, "Save search default")</button>
76 </form>
77 <div class="sv-group">
78 <div class="sv-group-header"><h4>Search shortcuts</h4></div>
79 <p class="sv-meta">Shortcut URLs must include the %s placeholder for the search query.</p>
80 <form class="sv-inline-form" data-sv-action="config.searchShortcut.create">
81 <label><span>Key</span><input name="key" type="text" placeholder="ddg" required/></label>
82 <label><span>URL</span><input name="url" type="text" placeholder="https://duckduckgo.com/?q=%s" pattern=".*%s.*" required/></label>
83 <label><span>Description</span><input name="description" type="text" placeholder="DuckDuckGo"/></label>
84 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconPlus, "Add shortcut")</button>
85 </form>
86 {{ shortcuts := searchShortcutRows(config.SearchShortcuts) }}
87 if len(shortcuts) == 0 {
88 @EmptyState("No search shortcuts configured")
89 } else {
90 <ul class="sv-list">
91 for _, shortcut := range shortcuts {
92 @SearchShortcutItem(shortcut)
93 }
94 </ul>
95 }
96 </div>
97}
98
99templ SearchShortcutItem(shortcut searchShortcutRow) {
100 <li class="sv-list-row">
101 <form class="sv-inline-form" data-sv-action="config.searchShortcut.update">
102 <input type="hidden" name="key" value={ shortcut.Key }/>
103 <label><span>Key</span><input name="new_key" type="text" value={ shortcut.Key } required/></label>
104 <label><span>URL</span><input name="url" type="text" value={ shortcut.URL } pattern=".*%s.*" required/></label>
105 <label><span>Description</span><input name="description" type="text" value={ shortcut.Description }/></label>
106 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
107 <button class="sv-button sv-button-secondary sv-button-danger sv-button-icon-only" type="button" data-sv-action="config.searchShortcut.delete" data-key={ shortcut.Key } data-sv-confirm="Delete this search shortcut?" aria-label={ "Delete search shortcut " + shortcut.Key }>@IconOnly(iconTrash, "Delete search shortcut " + shortcut.Key)</button>
108 </form>
109 </li>
110}
111
112templ PerformanceSettings(config dto.SystemviewConfigPayload) {
113 {{ customProfile := performanceProfileSelected(config.Performance.Profile, "custom") }}
114 <form class="sv-card-form sv-config-form" data-sv-action="config.performance.save" data-sv-performance-form>
115 <label><span>Profile</span><select name="profile" data-sv-performance-profile>
116 <option value="default" selected?={ performanceProfileSelected(config.Performance.Profile, "default") }>Default</option>
117 <option value="balanced" selected?={ performanceProfileSelected(config.Performance.Profile, "balanced") }>Balanced</option>
118 <option value="lite" selected?={ performanceProfileSelected(config.Performance.Profile, "lite") }>Lite</option>
119 <option value="max" selected?={ performanceProfileSelected(config.Performance.Profile, "max") }>Max</option>
120 <option value="custom" selected?={ performanceProfileSelected(config.Performance.Profile, "custom") }>Custom</option>
121 </select></label>
122 <p class="sv-meta">Choose Custom to edit individual performance values.</p>
123 <label><span>Skia CPU threads</span><input name="skia_cpu_threads" type="number" min="0" max="8" value={ formatConfigInt(config.Performance.Custom.SkiaCPUThreads) } disabled?={ !customProfile } data-sv-performance-custom/></label>
124 <label><span>Skia GPU threads</span><input name="skia_gpu_threads" type="number" min="-1" max="8" value={ formatConfigInt(config.Performance.Custom.SkiaGPUThreads) } disabled?={ !customProfile } data-sv-performance-custom/></label>
125 <label><span>Web process memory MB</span><input name="web_process_memory_mb" type="number" min="0" max="16384" value={ formatConfigInt(config.Performance.Custom.WebProcessMemoryMB) } disabled?={ !customProfile } data-sv-performance-custom/></label>
126 <label><span>Network process memory MB</span><input name="network_process_memory_mb" type="number" min="0" max="4096" value={ formatConfigInt(config.Performance.Custom.NetworkProcessMemoryMB) } disabled?={ !customProfile } data-sv-performance-custom/></label>
127 <label><span>WebView pool prewarm</span><input name="webview_pool_prewarm" type="number" min="0" max="20" value={ formatConfigInt(config.Performance.Custom.WebViewPoolPrewarm) } disabled?={ !customProfile } data-sv-performance-custom/></label>
128 <button class="sv-button" type="submit">@ButtonIconLabel(iconSave, "Save performance")</button>
129 </form>
130 <div class="sv-stat-grid">
131 <div class="sv-stat-card"><span class="sv-stat-value">{ formatConfigInt(config.Performance.Hardware.CPUThreads) }</span><span class="sv-stat-label">CPU threads</span></div>
132 <div class="sv-stat-card"><span class="sv-stat-value">{ formatConfigInt(config.Performance.Hardware.TotalRAMMB) }</span><span class="sv-stat-label">RAM MB</span></div>
133 <div class="sv-stat-card"><span class="sv-stat-value">{ config.Performance.Hardware.GPUName }</span><span class="sv-stat-label">GPU</span></div>
134 <div class="sv-stat-card"><span class="sv-stat-value">{ config.EngineType }</span><span class="sv-stat-label">Engine</span></div>
135 </div>
136 if !engineIsWebKit(config.EngineType) {
137 <p class="sv-meta">Performance tuning applies only to the WebKit engine.</p>
138 } else {
139 <p class="sv-meta">Performance profile changes may require restarting the browser to affect existing engine processes.</p>
140 }
141}
142
143templ ConfigKeybindings(keybindings port.KeybindingsConfig) {
144 {{ groups := configKeybindingGroups(keybindings) }}
145 if groups == nil {
146 @EmptyState("Keybindings unavailable")
147 } else if len(groups) == 0 {
148 @EmptyState("No keybindings configured")
149 } else {
150 <div class="sv-button-row">
151 <button class="sv-button sv-button-secondary sv-button-danger" type="button" data-sv-action="config.keybinding.resetAll" data-sv-confirm="Reset all keybindings to defaults?">@ButtonIconLabel(iconRotateCCW, "Reset all keybindings")</button>
152 </div>
153 for _, group := range groups {
154 @ConfigKeybindingGroup(group)
155 }
156 }
157}
158
159templ ConfigKeybindingGroup(group renderedKeybindingGroup) {
160 <div class="sv-group">
161 <div class="sv-group-header">
162 <div>
163 <h4>{ keybindingGroupTitle(group) }</h4>
164 if keybindingGroupMeta(group) != "" {
165 <p class="sv-meta">{ keybindingGroupMeta(group) }</p>
166 }
167 </div>
168 </div>
169 <div class="sv-group-body">
170 if len(group.Bindings) == 0 {
171 @EmptyState("No bindings in this group")
172 } else {
173 <ul class="sv-list">
174 for _, binding := range group.Bindings {
175 @ConfigKeybinding(group, binding)
176 }
177 </ul>
178 }
179 </div>
180 </div>
181}
182
183templ ConfigKeybinding(group renderedKeybindingGroup, binding renderedKeybinding) {
184 <li class="sv-list-row">
185 <div class="sv-keybinding-row">
186 <div>
187 <strong>{ keybindingDescription(binding) }</strong>
188 <p class="sv-meta">{ keybindingAction(binding) }</p>
189 <div class="sv-binding-keys">
190 @KeyList(binding.Keys)
191 <span class="sv-badge">{ keybindingStatus(binding) }</span>
192 if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
193 <span class="sv-meta">default:</span>
194 @KeyList(binding.DefaultKeys)
195 }
196 </div>
197 </div>
198 <form class="sv-inline-form" data-sv-action="config.keybinding.set">
199 <input type="hidden" name="mode" value={ group.Mode }/>
200 <input type="hidden" name="action" value={ binding.Action }/>
201 <label><span>Keys</span><input name="keys" type="text" value={ keybindingKeysValue(binding.Keys) } placeholder="ctrl+h, ctrl+shift+h" required/></label>
202 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
203 <button class="sv-button sv-button-secondary" type="button" data-sv-action="config.keybinding.reset" data-mode={ group.Mode } data-action={ binding.Action }>@ButtonIconLabel(iconRotateCCW, "Reset")</button>
204 </form>
205 </div>
206 </li>
207}