v0.29.0 21b3150

config.templ RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close