feat: add Cancel and Defaults buttons to Settings
Cancel discards unsaved edits by reloading the saved config; Defaults loads built-in default values into the form for review before saving. Extracts the default config into domain.DefaultConfig() so the store and settings UI share one source of truth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,25 @@ type Config struct {
|
|||||||
Theme Theme `json:"theme,omitempty"`
|
Theme Theme `json:"theme,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultConfig returns the built-in default settings. It is the config used
|
||||||
|
// when gosentry.json does not yet exist, and is also what the Settings UI
|
||||||
|
// offers to restore via its "Defaults" button.
|
||||||
|
func DefaultConfig() Config {
|
||||||
|
return Config{
|
||||||
|
JobsDir: ".",
|
||||||
|
LogsDir: "logs",
|
||||||
|
MaxLogFiles: 100,
|
||||||
|
MaxLogAgeDays: 30,
|
||||||
|
StartOnLogin: false,
|
||||||
|
KeepRunningInTray: true,
|
||||||
|
NotifyOnFailure: true,
|
||||||
|
ExecutionMode: ExecutionModeParallel,
|
||||||
|
OverlapPolicy: OverlapPolicySkip,
|
||||||
|
Theme: ThemeDefault,
|
||||||
|
DefaultTimeoutSeconds: 30,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// JobsFile is the on-disk shape of jobs.json. Wrapping the slice in a top-level
|
// JobsFile is the on-disk shape of jobs.json. Wrapping the slice in a top-level
|
||||||
// object leaves room for future metadata without breaking the basic file format.
|
// object leaves room for future metadata without breaking the basic file format.
|
||||||
type JobsFile struct {
|
type JobsFile struct {
|
||||||
|
|||||||
+1
-14
@@ -68,20 +68,7 @@ func (s *Store) SaveJobs(jobs []domain.Job) error {
|
|||||||
func loadOrCreateConfig(paths Paths) (domain.Config, error) {
|
func loadOrCreateConfig(paths Paths) (domain.Config, error) {
|
||||||
// Defaults favor a portable installation: settings and jobs begin next to the
|
// Defaults favor a portable installation: settings and jobs begin next to the
|
||||||
// executable, while logs are grouped under a dedicated subdirectory.
|
// executable, while logs are grouped under a dedicated subdirectory.
|
||||||
config := domain.Config{
|
config := domain.DefaultConfig()
|
||||||
JobsDir: ".",
|
|
||||||
LogsDir: "logs",
|
|
||||||
MaxLogFiles: 100,
|
|
||||||
MaxLogAgeDays: 30,
|
|
||||||
StartOnLogin: false,
|
|
||||||
KeepRunningInTray: true,
|
|
||||||
NotifyOnFailure: true,
|
|
||||||
ExecutionMode: domain.ExecutionModeParallel,
|
|
||||||
OverlapPolicy: domain.OverlapPolicySkip,
|
|
||||||
Theme: domain.ThemeDefault,
|
|
||||||
|
|
||||||
DefaultTimeoutSeconds: 30,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(paths.ConfigPath); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(paths.ConfigPath); errors.Is(err, os.ErrNotExist) {
|
||||||
return config, writeJSON(paths.ConfigPath, config)
|
return config, writeJSON(paths.ConfigPath, config)
|
||||||
|
|||||||
+39
-4
@@ -35,6 +35,9 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
// when something differs. It is defined below (once Save and every field
|
// when something differs. It is defined below (once Save and every field
|
||||||
// exist) but declared here so the field change handlers can reference it.
|
// exist) but declared here so the field change handlers can reference it.
|
||||||
var updateSaveState func()
|
var updateSaveState func()
|
||||||
|
// loadFields populates every form control from the given config. It backs
|
||||||
|
// both the initial load and the Cancel/Defaults buttons below.
|
||||||
|
var loadFields func(domain.Config)
|
||||||
startOnLogin := widget.NewCheck("Start on login", nil)
|
startOnLogin := widget.NewCheck("Start on login", nil)
|
||||||
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
||||||
autostartStatus := widget.NewLabel("")
|
autostartStatus := widget.NewLabel("")
|
||||||
@@ -188,6 +191,38 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
}
|
}
|
||||||
updateSaveState()
|
updateSaveState()
|
||||||
|
|
||||||
|
// loadFields populates every form control from a config without saving it,
|
||||||
|
// backing both the Cancel button (reload the saved config, discarding edits)
|
||||||
|
// and the Defaults button (load the built-in defaults for review before
|
||||||
|
// Save is clicked).
|
||||||
|
loadFields = func(c domain.Config) {
|
||||||
|
startOnLogin.SetChecked(c.StartOnLogin)
|
||||||
|
minimizeToTray.SetChecked(c.KeepRunningInTray)
|
||||||
|
notifications.SetChecked(c.NotifyOnFailure)
|
||||||
|
themeSelect.SetSelected(themeLabel(c.Theme))
|
||||||
|
applyTheme(fyne.CurrentApp(), themeFromLabel(themeSelect.Selected))
|
||||||
|
executionModeSelect.SetSelected(string(c.ExecutionMode))
|
||||||
|
overlapPolicySelect.SetSelected(string(c.OverlapPolicy))
|
||||||
|
defaultTimeout.SetText(strconv.Itoa(c.DefaultTimeoutSeconds))
|
||||||
|
jobsDir.SetText(c.JobsDir)
|
||||||
|
logsDir.SetText(c.LogsDir)
|
||||||
|
maxLogFiles.SetText(strconv.Itoa(c.MaxLogFiles))
|
||||||
|
maxLogAgeDays.SetText(strconv.Itoa(c.MaxLogAgeDays))
|
||||||
|
if startOnLogin.Checked != store.Config.StartOnLogin {
|
||||||
|
autostartStatus.SetText("Pending: save settings to apply")
|
||||||
|
} else {
|
||||||
|
refreshAutostartStatus()
|
||||||
|
}
|
||||||
|
settingsStatus.SetText("")
|
||||||
|
updateSaveState()
|
||||||
|
}
|
||||||
|
cancelSettings := widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func() {
|
||||||
|
loadFields(store.Config)
|
||||||
|
})
|
||||||
|
restoreDefaults := widget.NewButtonWithIcon("Defaults", theme.MediaReplayIcon(), func() {
|
||||||
|
loadFields(domain.DefaultConfig())
|
||||||
|
})
|
||||||
|
|
||||||
// The form is split into two columns so a wide window uses its horizontal
|
// The form is split into two columns so a wide window uses its horizontal
|
||||||
// space instead of stretching into one tall strip. The left column holds the
|
// space instead of stretching into one tall strip. The left column holds the
|
||||||
// toggles (Application, Queue); the right holds the editable Storage fields and
|
// toggles (Application, Queue); the right holds the editable Storage fields and
|
||||||
@@ -241,10 +276,10 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|||||||
return container.NewVScroll(container.NewPadded(container.NewVBox(
|
return container.NewVScroll(container.NewPadded(container.NewVBox(
|
||||||
container.NewGridWithColumns(2, leftColumn, rightColumn),
|
container.NewGridWithColumns(2, leftColumn, rightColumn),
|
||||||
widget.NewSeparator(),
|
widget.NewSeparator(),
|
||||||
// Save button and its status share one row so an empty status (the common
|
// Save/Cancel/Defaults share one row with the status so an empty status
|
||||||
// case) does not leave a blank line above the separator. The status appears
|
// (the common case) does not leave a blank line above the separator. The
|
||||||
// beside the button once a save reports a result.
|
// status appears beside the buttons once a save reports a result.
|
||||||
container.NewHBox(saveSettings, settingsStatus),
|
container.NewHBox(saveSettings, cancelSettings, restoreDefaults, settingsStatus),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user