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 @PaletteFields("Light palette", "light", config.Appearance.LightPalette)
43 @PaletteFields("Dark palette", "dark", config.Appearance.DarkPalette)
44 <div class="sv-button-row sv-form-actions">
45 <button class="sv-button" type="submit">@ButtonIconLabel(iconSave, "Save appearance")</button>
46 <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>
47 </div>
48 </form>
49}
50
51templ PaletteFields(title string, prefix string, palette dto.ColorPalette) {
52 <fieldset class="sv-fieldset">
53 <legend>{ title }</legend>
54 <label><span>Background</span><input name={ prefix + "_background" } type="text" value={ palette.Background } required/></label>
55 <label><span>Surface</span><input name={ prefix + "_surface" } type="text" value={ palette.Surface } required/></label>
56 <label><span>Surface variant</span><input name={ prefix + "_surface_variant" } type="text" value={ palette.SurfaceVariant } required/></label>
57 <label><span>Text</span><input name={ prefix + "_text" } type="text" value={ palette.Text } required/></label>
58 <label><span>Muted</span><input name={ prefix + "_muted" } type="text" value={ palette.Muted } required/></label>
59 <label><span>Accent</span><input name={ prefix + "_accent" } type="text" value={ palette.Accent } required/></label>
60 <label><span>Border</span><input name={ prefix + "_border" } type="text" value={ palette.Border } required/></label>
61 </fieldset>
62}
63
64templ SearchSettings(config dto.SystemviewConfigPayload) {
65 <form class="sv-card-form" data-sv-action="config.search.save">
66 <label><span>Default search engine</span><input name="default_search_engine" type="text" value={ config.DefaultSearchEngine } placeholder="https://duckduckgo.com/?q=%s" required/></label>
67 <button class="sv-button" type="submit">@ButtonIconLabel(iconSave, "Save search default")</button>
68 </form>
69 <div class="sv-group">
70 <div class="sv-group-header"><h4>Search shortcuts</h4></div>
71 <p class="sv-meta">Shortcut URLs must include the %s placeholder for the search query.</p>
72 <form class="sv-inline-form" data-sv-action="config.searchShortcut.create">
73 <label><span>Key</span><input name="key" type="text" placeholder="ddg" required/></label>
74 <label><span>URL</span><input name="url" type="text" placeholder="https://duckduckgo.com/?q=%s" pattern=".*%s.*" required/></label>
75 <label><span>Description</span><input name="description" type="text" placeholder="DuckDuckGo"/></label>
76 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconPlus, "Add shortcut")</button>
77 </form>
78 {{ shortcuts := searchShortcutRows(config.SearchShortcuts) }}
79 if len(shortcuts) == 0 {
80 @EmptyState("No search shortcuts configured")
81 } else {
82 <ul class="sv-list">
83 for _, shortcut := range shortcuts {
84 @SearchShortcutItem(shortcut)
85 }
86 </ul>
87 }
88 </div>
89}
90
91templ SearchShortcutItem(shortcut searchShortcutRow) {
92 <li class="sv-list-row">
93 <form class="sv-inline-form" data-sv-action="config.searchShortcut.update">
94 <input type="hidden" name="key" value={ shortcut.Key }/>
95 <label><span>Key</span><input name="new_key" type="text" value={ shortcut.Key } required/></label>
96 <label><span>URL</span><input name="url" type="text" value={ shortcut.URL } pattern=".*%s.*" required/></label>
97 <label><span>Description</span><input name="description" type="text" value={ shortcut.Description }/></label>
98 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
99 <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>
100 </form>
101 </li>
102}
103
104templ PerformanceSettings(config dto.SystemviewConfigPayload) {
105 {{ customProfile := performanceProfileSelected(config.Performance.Profile, "custom") }}
106 <form class="sv-card-form sv-config-form" data-sv-action="config.performance.save" data-sv-performance-form>
107 <label><span>Profile</span><select name="profile" data-sv-performance-profile>
108 <option value="default" selected?={ performanceProfileSelected(config.Performance.Profile, "default") }>Default</option>
109 <option value="balanced" selected?={ performanceProfileSelected(config.Performance.Profile, "balanced") }>Balanced</option>
110 <option value="lite" selected?={ performanceProfileSelected(config.Performance.Profile, "lite") }>Lite</option>
111 <option value="max" selected?={ performanceProfileSelected(config.Performance.Profile, "max") }>Max</option>
112 <option value="custom" selected?={ performanceProfileSelected(config.Performance.Profile, "custom") }>Custom</option>
113 </select></label>
114 <p class="sv-meta">Choose Custom to edit individual performance values.</p>
115 <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>
116 <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>
117 <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>
118 <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>
119 <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>
120 <button class="sv-button" type="submit">@ButtonIconLabel(iconSave, "Save performance")</button>
121 </form>
122 <div class="sv-stat-grid">
123 <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>
124 <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>
125 <div class="sv-stat-card"><span class="sv-stat-value">{ config.Performance.Hardware.GPUName }</span><span class="sv-stat-label">GPU</span></div>
126 <div class="sv-stat-card"><span class="sv-stat-value">{ config.EngineType }</span><span class="sv-stat-label">Engine</span></div>
127 </div>
128 if !engineIsWebKit(config.EngineType) {
129 <p class="sv-meta">Performance tuning applies only to the WebKit engine.</p>
130 } else {
131 <p class="sv-meta">Performance profile changes may require restarting the browser to affect existing engine processes.</p>
132 }
133}
134
135templ ConfigKeybindings(keybindings port.KeybindingsConfig) {
136 {{ groups := configKeybindingGroups(keybindings) }}
137 if groups == nil {
138 @EmptyState("Keybindings unavailable")
139 } else if len(groups) == 0 {
140 @EmptyState("No keybindings configured")
141 } else {
142 <div class="sv-button-row">
143 <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>
144 </div>
145 for _, group := range groups {
146 @ConfigKeybindingGroup(group)
147 }
148 }
149}
150
151templ ConfigKeybindingGroup(group renderedKeybindingGroup) {
152 <div class="sv-group">
153 <div class="sv-group-header">
154 <div>
155 <h4>{ keybindingGroupTitle(group) }</h4>
156 if keybindingGroupMeta(group) != "" {
157 <p class="sv-meta">{ keybindingGroupMeta(group) }</p>
158 }
159 </div>
160 </div>
161 <div class="sv-group-body">
162 if len(group.Bindings) == 0 {
163 @EmptyState("No bindings in this group")
164 } else {
165 <ul class="sv-list">
166 for _, binding := range group.Bindings {
167 @ConfigKeybinding(group, binding)
168 }
169 </ul>
170 }
171 </div>
172 </div>
173}
174
175templ ConfigKeybinding(group renderedKeybindingGroup, binding renderedKeybinding) {
176 <li class="sv-list-row">
177 <div class="sv-keybinding-row">
178 <div>
179 <strong>{ keybindingDescription(binding) }</strong>
180 <p class="sv-meta">{ keybindingAction(binding) }</p>
181 <div class="sv-binding-keys">
182 @KeyList(binding.Keys)
183 <span class="sv-badge">{ keybindingStatus(binding) }</span>
184 if keybindingIsCustom(binding) && len(binding.DefaultKeys) > 0 {
185 <span class="sv-meta">default:</span>
186 @KeyList(binding.DefaultKeys)
187 }
188 </div>
189 </div>
190 <form class="sv-inline-form" data-sv-action="config.keybinding.set">
191 <input type="hidden" name="mode" value={ group.Mode }/>
192 <input type="hidden" name="action" value={ binding.Action }/>
193 <label><span>Keys</span><input name="keys" type="text" value={ keybindingKeysValue(binding.Keys) } placeholder="ctrl+h, ctrl+shift+h" required/></label>
194 <button class="sv-button sv-button-secondary" type="submit">@ButtonIconLabel(iconSave, "Save")</button>
195 <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>
196 </form>
197 </div>
198 </li>
199}