From d9b11db30c6e6832af61815bda96397600452aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 15:46:20 -0400 Subject: [PATCH] WIP components (as built) --- accent_color_picker.go | 106 +++++++ accent_color_picker_test.go | 74 +++++ account_switcher.go | 150 ++++++++++ account_switcher_test.go | 139 +++++++++ api_key_manager.go | 204 +++++++++++++ api_key_manager_test.go | 201 +++++++++++++ coachmark.go | 93 ++++++ coachmark_test.go | 79 +++++ docs/components/461-settings-layout.md | 53 ++++ docs/components/462-settings-search.md | 48 +++ docs/components/463-theme-selector.md | 41 +++ docs/components/464-accent-color-picker.md | 42 +++ docs/components/465-shortcut-recorder.md | 46 +++ .../components/466-keyboard-shortcuts-list.md | 47 +++ docs/components/467-login-form.md | 47 +++ docs/components/468-oauth-buttons.md | 44 +++ docs/components/469-two-factor-input.md | 48 +++ docs/components/470-profile-editor.md | 48 +++ docs/components/471-account-switcher.md | 47 +++ docs/components/472-workspace-switcher.md | 45 +++ docs/components/473-user-menu.md | 46 +++ docs/components/474-session-list.md | 45 +++ docs/components/475-api-key-manager.md | 51 ++++ docs/components/476-usage-quota.md | 42 +++ docs/components/477-onboarding-wizard.md | 45 +++ docs/components/478-coachmark.md | 43 +++ docs/components/479-whats-new-dialog.md | 44 +++ docs/components/480-feedback-widget.md | 46 +++ example_account_test.go | 240 +++++++++++++++ examples/gallery/pages_account.go | 154 +++++++++- examples/gallery/pages_account_auth.go | 159 ++++++++++ examples/gallery/pages_account_code.go | 175 +++++++++++ examples/gallery/pages_account_more.go | 276 ++++++++++++++++++ examples/gallery/zz_account_test.go | 102 +++++++ feedback_widget.go | 118 ++++++++ feedback_widget_test.go | 121 ++++++++ keyboard_shortcuts_list.go | 109 +++++++ keyboard_shortcuts_list_test.go | 106 +++++++ login_form.go | 99 +++++++ login_form_test.go | 93 ++++++ messages_account.go | 123 ++++++++ oauth_buttons.go | 58 ++++ oauth_buttons_test.go | 62 ++++ onboarding_wizard.go | 97 ++++++ onboarding_wizard_test.go | 94 ++++++ overlay_layers.go | 9 +- overlay_test.go | 3 +- profile_editor.go | 103 +++++++ profile_editor_test.go | 104 +++++++ session_list.go | 124 ++++++++ session_list_test.go | 80 +++++ settings_layout.go | 139 +++++++++ settings_layout_test.go | 103 +++++++ settings_search.go | 188 ++++++++++++ settings_search_test.go | 122 ++++++++ shortcut_recorder.go | 217 ++++++++++++++ shortcut_recorder_test.go | 124 ++++++++ .../darwin/accent_color_picker-dark.png | Bin 0 -> 8108 bytes .../darwin/accent_color_picker-light.png | Bin 0 -> 8141 bytes .../darwin/account_switcher-dark.png | Bin 0 -> 5659 bytes .../darwin/account_switcher-light.png | Bin 0 -> 5483 bytes .../snapshots/darwin/api_key_manager-dark.png | Bin 0 -> 12759 bytes .../darwin/api_key_manager-light.png | Bin 0 -> 12332 bytes testdata/snapshots/darwin/coachmark-dark.png | Bin 0 -> 13357 bytes testdata/snapshots/darwin/coachmark-light.png | Bin 0 -> 14937 bytes .../snapshots/darwin/feedback_widget-dark.png | Bin 0 -> 18450 bytes .../darwin/feedback_widget-light.png | Bin 0 -> 17776 bytes .../darwin/keyboard_shortcuts_list-dark.png | Bin 0 -> 17611 bytes .../darwin/keyboard_shortcuts_list-light.png | Bin 0 -> 17007 bytes testdata/snapshots/darwin/login_form-dark.png | Bin 0 -> 15783 bytes .../snapshots/darwin/login_form-light.png | Bin 0 -> 15314 bytes .../snapshots/darwin/oauth_buttons-dark.png | Bin 0 -> 7815 bytes .../snapshots/darwin/oauth_buttons-light.png | Bin 0 -> 7433 bytes .../darwin/onboarding_wizard-dark.png | Bin 0 -> 20777 bytes .../darwin/onboarding_wizard-light.png | Bin 0 -> 20079 bytes .../snapshots/darwin/profile_editor-dark.png | Bin 0 -> 20695 bytes .../snapshots/darwin/profile_editor-light.png | Bin 0 -> 20000 bytes .../snapshots/darwin/session_list-dark.png | Bin 0 -> 23319 bytes .../snapshots/darwin/session_list-light.png | Bin 0 -> 22742 bytes .../snapshots/darwin/settings_layout-dark.png | Bin 0 -> 16177 bytes .../darwin/settings_layout-light.png | Bin 0 -> 15917 bytes .../snapshots/darwin/settings_search-dark.png | Bin 0 -> 7424 bytes .../darwin/settings_search-light.png | Bin 0 -> 7258 bytes .../darwin/shortcut_recorder-dark.png | Bin 0 -> 2450 bytes .../darwin/shortcut_recorder-light.png | Bin 0 -> 2442 bytes .../snapshots/darwin/theme_selector-dark.png | Bin 0 -> 6335 bytes .../snapshots/darwin/theme_selector-light.png | Bin 0 -> 6221 bytes .../darwin/two_factor_input-dark.png | Bin 0 -> 5082 bytes .../darwin/two_factor_input-light.png | Bin 0 -> 5009 bytes .../snapshots/darwin/usage_quota-dark.png | Bin 0 -> 14269 bytes .../snapshots/darwin/usage_quota-light.png | Bin 0 -> 13780 bytes testdata/snapshots/darwin/user_menu-dark.png | Bin 0 -> 2892 bytes testdata/snapshots/darwin/user_menu-light.png | Bin 0 -> 2813 bytes .../darwin/whats_new_dialog-dark.png | Bin 0 -> 25019 bytes .../darwin/whats_new_dialog-light.png | Bin 0 -> 25989 bytes .../darwin/workspace_switcher-dark.png | Bin 0 -> 4538 bytes .../darwin/workspace_switcher-light.png | Bin 0 -> 4455 bytes theme_selector.go | 88 ++++++ theme_selector_test.go | 48 +++ two_factor_input.go | 75 +++++ two_factor_input_test.go | 96 ++++++ usage_quota.go | 93 ++++++ usage_quota_test.go | 60 ++++ user_menu.go | 110 +++++++ user_menu_test.go | 76 +++++ whats_new_dialog.go | 99 +++++++ whats_new_dialog_test.go | 67 +++++ workspace_switcher.go | 141 +++++++++ workspace_switcher_test.go | 93 ++++++ 109 files changed, 6509 insertions(+), 3 deletions(-) create mode 100644 accent_color_picker.go create mode 100644 accent_color_picker_test.go create mode 100644 account_switcher.go create mode 100644 account_switcher_test.go create mode 100644 api_key_manager.go create mode 100644 api_key_manager_test.go create mode 100644 coachmark.go create mode 100644 coachmark_test.go create mode 100644 docs/components/461-settings-layout.md create mode 100644 docs/components/462-settings-search.md create mode 100644 docs/components/463-theme-selector.md create mode 100644 docs/components/464-accent-color-picker.md create mode 100644 docs/components/465-shortcut-recorder.md create mode 100644 docs/components/466-keyboard-shortcuts-list.md create mode 100644 docs/components/467-login-form.md create mode 100644 docs/components/468-oauth-buttons.md create mode 100644 docs/components/469-two-factor-input.md create mode 100644 docs/components/470-profile-editor.md create mode 100644 docs/components/471-account-switcher.md create mode 100644 docs/components/472-workspace-switcher.md create mode 100644 docs/components/473-user-menu.md create mode 100644 docs/components/474-session-list.md create mode 100644 docs/components/475-api-key-manager.md create mode 100644 docs/components/476-usage-quota.md create mode 100644 docs/components/477-onboarding-wizard.md create mode 100644 docs/components/478-coachmark.md create mode 100644 docs/components/479-whats-new-dialog.md create mode 100644 docs/components/480-feedback-widget.md create mode 100644 example_account_test.go create mode 100644 examples/gallery/pages_account_auth.go create mode 100644 examples/gallery/pages_account_code.go create mode 100644 examples/gallery/pages_account_more.go create mode 100644 examples/gallery/zz_account_test.go create mode 100644 feedback_widget.go create mode 100644 feedback_widget_test.go create mode 100644 keyboard_shortcuts_list.go create mode 100644 keyboard_shortcuts_list_test.go create mode 100644 login_form.go create mode 100644 login_form_test.go create mode 100644 messages_account.go create mode 100644 oauth_buttons.go create mode 100644 oauth_buttons_test.go create mode 100644 onboarding_wizard.go create mode 100644 onboarding_wizard_test.go create mode 100644 profile_editor.go create mode 100644 profile_editor_test.go create mode 100644 session_list.go create mode 100644 session_list_test.go create mode 100644 settings_layout.go create mode 100644 settings_layout_test.go create mode 100644 settings_search.go create mode 100644 settings_search_test.go create mode 100644 shortcut_recorder.go create mode 100644 shortcut_recorder_test.go create mode 100644 testdata/snapshots/darwin/accent_color_picker-dark.png create mode 100644 testdata/snapshots/darwin/accent_color_picker-light.png create mode 100644 testdata/snapshots/darwin/account_switcher-dark.png create mode 100644 testdata/snapshots/darwin/account_switcher-light.png create mode 100644 testdata/snapshots/darwin/api_key_manager-dark.png create mode 100644 testdata/snapshots/darwin/api_key_manager-light.png create mode 100644 testdata/snapshots/darwin/coachmark-dark.png create mode 100644 testdata/snapshots/darwin/coachmark-light.png create mode 100644 testdata/snapshots/darwin/feedback_widget-dark.png create mode 100644 testdata/snapshots/darwin/feedback_widget-light.png create mode 100644 testdata/snapshots/darwin/keyboard_shortcuts_list-dark.png create mode 100644 testdata/snapshots/darwin/keyboard_shortcuts_list-light.png create mode 100644 testdata/snapshots/darwin/login_form-dark.png create mode 100644 testdata/snapshots/darwin/login_form-light.png create mode 100644 testdata/snapshots/darwin/oauth_buttons-dark.png create mode 100644 testdata/snapshots/darwin/oauth_buttons-light.png create mode 100644 testdata/snapshots/darwin/onboarding_wizard-dark.png create mode 100644 testdata/snapshots/darwin/onboarding_wizard-light.png create mode 100644 testdata/snapshots/darwin/profile_editor-dark.png create mode 100644 testdata/snapshots/darwin/profile_editor-light.png create mode 100644 testdata/snapshots/darwin/session_list-dark.png create mode 100644 testdata/snapshots/darwin/session_list-light.png create mode 100644 testdata/snapshots/darwin/settings_layout-dark.png create mode 100644 testdata/snapshots/darwin/settings_layout-light.png create mode 100644 testdata/snapshots/darwin/settings_search-dark.png create mode 100644 testdata/snapshots/darwin/settings_search-light.png create mode 100644 testdata/snapshots/darwin/shortcut_recorder-dark.png create mode 100644 testdata/snapshots/darwin/shortcut_recorder-light.png create mode 100644 testdata/snapshots/darwin/theme_selector-dark.png create mode 100644 testdata/snapshots/darwin/theme_selector-light.png create mode 100644 testdata/snapshots/darwin/two_factor_input-dark.png create mode 100644 testdata/snapshots/darwin/two_factor_input-light.png create mode 100644 testdata/snapshots/darwin/usage_quota-dark.png create mode 100644 testdata/snapshots/darwin/usage_quota-light.png create mode 100644 testdata/snapshots/darwin/user_menu-dark.png create mode 100644 testdata/snapshots/darwin/user_menu-light.png create mode 100644 testdata/snapshots/darwin/whats_new_dialog-dark.png create mode 100644 testdata/snapshots/darwin/whats_new_dialog-light.png create mode 100644 testdata/snapshots/darwin/workspace_switcher-dark.png create mode 100644 testdata/snapshots/darwin/workspace_switcher-light.png create mode 100644 theme_selector.go create mode 100644 theme_selector_test.go create mode 100644 two_factor_input.go create mode 100644 two_factor_input_test.go create mode 100644 usage_quota.go create mode 100644 usage_quota_test.go create mode 100644 user_menu.go create mode 100644 user_menu_test.go create mode 100644 whats_new_dialog.go create mode 100644 whats_new_dialog_test.go create mode 100644 workspace_switcher.go create mode 100644 workspace_switcher_test.go diff --git a/accent_color_picker.go b/accent_color_picker.go new file mode 100644 index 0000000..7fed104 --- /dev/null +++ b/accent_color_picker.go @@ -0,0 +1,106 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// AccentColorPickerOptions configure an AccentColorPicker. +type AccentColorPickerOptions struct { + // Candidates defaults to the built-in court palette. + Candidates []ui.Color + Disabled bool +} + +// accentPickerColors offers readable accents and marked low-contrast examples. +var accentPickerColors = []ui.Color{ + ui.Hex("#7D2034"), ui.Hex("#355E80"), ui.Hex("#326247"), ui.Hex("#4B3A5A"), + ui.Hex("#805714"), ui.Hex("#211C1D"), ui.Hex("#AD9164"), ui.Hex("#D9A5B3"), +} + +// accentPickerReadable reports whether accent carries OnAccent text at 4.5:1. +func accentPickerReadable(k theme.Tokens, accent ui.Color) bool { + return theme.Contrast(k.OnAccent, accent) >= 4.5 +} + +// AccentColorPicker selects an accent and marks insufficient contrast. +func AccentColorPicker(c *ui.Context, color *ui.Color, opts AccentColorPickerOptions) ChoiceResult { + cands := opts.Candidates + if cands == nil { + cands = accentPickerColors + } + if len(cands) == 0 { + panic("mujica: AccentColorPicker needs candidates") + } + k := Tokens(c) + d := Density(c) + var r ChoiceResult + r.Element = ui.Column(c).Gap(d.Unit() * 2) + r.Element.Children(func() { + low := false + ui.Row(c).Gap(d.Unit() * 2).Wrap().Role(ui.RoleRadioGroup).Label(msg(c, "accent_color_picker.label")). + FocusGroup(ui.Horizontal).Disabled(opts.Disabled).Children(func() { + selected := color + if r.Element.IsDisabled() || opts.Disabled { + copy := *color + selected = © + } + for _, col := range cands { + ok := accentPickerReadable(k, col) + low = low || !ok + hex := strings.ToUpper(colorPickerHex(col)) + sw := ui.RadioBase(c, selected, col).Size(36, 36).Center().Radius(theme.CardRadius).Label(hex) + if !ok { + sw.Description(msg(c, "accent_color_picker.low")) + } + if sw.Changed() && !sw.IsDisabled() { + r.changed = true + slog.Debug("mujica: accent_color_picker chose", "color", hex) + } + internal.Ring(sw, k.Focus, theme.CardRadius) + on := *color == col + sw.Draw(func(p *ui.Painter, rect ui.Rect) { + p.Fill(rect, col, theme.CardRadius) + p.Stroke(rect, k.Border, theme.CardRadius, 1) + if on { + p.Stroke(ui.Rect{X: rect.X - 3, Y: rect.Y - 3, W: rect.W + 6, H: rect.H + 6}, k.Text, theme.CardRadius+3, 1.5) + } + }) + sw.Children(func() { + switch { + case on: + ui.Icon(c, icons.Must("check")).Size(16, 16).TextColor(accentPickerMark(col)) + case !ok: + ui.Icon(c, icons.Must("triangle-alert")).Size(14, 14).TextColor(accentPickerMark(col)) + } + }) + } + }) + if low { + ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).TextColor(k.TextMuted).Children(func() { + ui.Icon(c, icons.Must("triangle-alert")).Size(12, 12) + ui.Text(c, msg(c, "accent_color_picker.low_note")).FontSize(FontSize(c, theme.CaptionSize)) + }) + } + if !accentPickerReadable(k, *color) { + ui.Text(c, fmt.Sprintf(msg(c, "accent_color_picker.chosen_low"), strings.ToUpper(colorPickerHex(*color)))). + TextColor(k.Warning).FontSize(FontSize(c, theme.CaptionSize)).Role(ui.RoleStatus) + } + }) + return r +} + +// accentPickerMark is black or white, whichever reads on col. +func accentPickerMark(col ui.Color) ui.Color { + if theme.Contrast(col, ui.RGB(255, 255, 255)) >= theme.Contrast(col, ui.RGB(0, 0, 0)) { + return ui.RGB(255, 255, 255) + } + return ui.RGB(0, 0, 0) +} diff --git a/accent_color_picker_test.go b/accent_color_picker_test.go new file mode 100644 index 0000000..e59e3f0 --- /dev/null +++ b/accent_color_picker_test.go @@ -0,0 +1,74 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +func TestAccentColorPickerMarksLowContrast(t *testing.T) { + col, n := ui.Hex("#7D2034"), 0 + cands := []ui.Color{ui.Hex("#7D2034"), ui.Hex("#F0E68C")} + tt := accountTester(400, 200, func(c *ui.Context) { + if AccentColorPicker(c, &col, AccentColorPickerOptions{Candidates: cands}).Changed() { + n++ + } + }) + k := theme.Light() + if !accentPickerReadable(k, cands[0]) || accentPickerReadable(k, cands[1]) { + t.Fatal("contrast check wrong") + } + if !tt.HasText("Marked colors are too faint for text on them.") { + t.Fatalf("no low-contrast note: %q", tt.Texts()) + } + if err := tt.Click("#F0E68C"); err != nil { + t.Fatal(err) + } + if col != cands[1] || n != 1 || !tt.HasText("#F0E68C has low contrast; text on it may be hard to read.") { + t.Fatalf("pick: %v %d %q", col, n, tt.Texts()) + } + tt.Key(0, ui.KeyLeft) + tt.Key(0, ui.KeySpace) + if col != cands[0] || n != 2 { + t.Fatalf("keys: %v", col) + } +} + +// Built-in accents meet their promised light-theme contrast. +func TestAccentColorPickerDefaults(t *testing.T) { + low := 0 + for _, col := range accentPickerColors { + if !accentPickerReadable(theme.Light(), col) { + low++ + } + } + if low != 2 { + t.Fatalf("%d defaults fail; the palette shows two faint ones on purpose", low) + } +} + +func TestAccentColorPickerSnapshot(t *testing.T) { + snapshot(t, "accent_color_picker", 420, 120, func(c *ui.Context) { + col := ui.Local(c.Root(), "col", func() ui.Color { return ui.Hex("#355E80") }) + ui.Column(c).Padding(16).Children(func() { AccentColorPicker(c, col, AccentColorPickerOptions{}) }) + }) +} + +func TestAccentColorPickerDisabledAndEmptyCandidates(t *testing.T) { + col := ui.Hex("#7D2034") + tt := accountTester(600, 300, func(c *ui.Context) { + ui.Column(c).Disabled(true).Children(func() { AccentColorPicker(c, &col, AccentColorPickerOptions{}) }) + }) + if err := tt.Click("#355E80"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyRight) + tt.Key(0, ui.KeySpace) + if col != ui.Hex("#7D2034") { + t.Fatal("disabled swatch changes") + } + accountPanics(t, func(c *ui.Context) { AccentColorPicker(c, &col, AccentColorPickerOptions{Candidates: []ui.Color{}}) }) +} diff --git a/account_switcher.go b/account_switcher.go new file mode 100644 index 0000000..786c654 --- /dev/null +++ b/account_switcher.go @@ -0,0 +1,150 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// Account is a signed-in account a user can switch to. +type Account struct { + ID, Name, Email string + Avatar *ui.Bitmap +} + +// AccountSwitcherOptions configure an AccountSwitcher. +type AccountSwitcherOptions struct { + Disabled bool +} + +// AccountSwitcherResult reports account changes and addition requests. +type AccountSwitcherResult struct { + Element *ui.Element + changed bool + add bool +} + +// Changed reports a switch to another account. +func (r AccountSwitcherResult) Changed() bool { return r.changed } + +// Add reports a request to sign in another account. +func (r AccountSwitcherResult) Add() bool { return r.add } + +// AccountSwitcher offers account switching and an addition action. +func AccountSwitcher(c *ui.Context, current *string, accounts []Account, opts AccountSwitcherOptions) AccountSwitcherResult { + var cur *Account + for i := range accounts { + if accounts[i].ID == "" || accounts[i].Name == "" { + panic("mujica: Account needs an ID and a Name") + } + if accounts[i].ID == *current { + cur = &accounts[i] + } + } + if cur == nil { + panic(fmt.Sprintf("mujica: AccountSwitcher current %q is not an account", *current)) + } + var r AccountSwitcherResult + b := accountTrigger(c, cur.Name, cur.Email, cur.Avatar, opts.Disabled) + r.Element = b + open := ui.Local(b, "open", func() bool { return false }) + if b.IsDisabled() { + *open = false + } else if b.Clicked() || b.Shortcut(0, ui.KeyDown) { + *open = true + } + if len(accounts) > 8 { + accountSwitcherLarge(c, b, open, current, accounts, &r) + return r + } + popupMenuOpen(c, b, open, func(m *PopupMenu) { + m.Group(msg(c, "account_switcher.accounts"), func() { + for _, a := range accounts { + on := a.ID == *current + label := a.Name + if a.Email != "" { + label += " · " + a.Email + } + ui.Column(c).Key(a.ID).Shrink(0).Children(func() { + if m.Check(label, &on, PopupMenuItemOptions{}) && a.ID != *current && !b.IsDisabled() { + *current, r.changed = a.ID, true + slog.Debug("mujica: account_switcher switched") + } + }) + } + }) + m.Separator() + if m.Item(msg(c, "account_switcher.add"), PopupMenuItemOptions{}) && !b.IsDisabled() { + r.add = true + slog.Debug("mujica: account_switcher add") + } + }) + return r +} + +func accountSwitcherLarge(c *ui.Context, b *ui.Element, open *bool, current *string, accounts []Account, r *AccountSwitcherResult) { + Popover(c, b, open, PopoverOptions{Label: msg(c, "account_switcher.accounts"), Width: 320}, func() { + cursor := ui.Local(b, "cursor", func() int { return -1 }) + rows := ui.Local(b, "list", func() ui.ListState { return ui.ListState{} }) + rows.Key = func(i int) any { return accounts[i].ID } + rows.Label = func(i int) string { return accounts[i].Name } + rows.Selected = cursor + restore := dataSelectTheme(c, 0) + list := ui.List(c, rows, len(accounts), func(i int) { + a := accounts[i] + row := workspaceRow(c, Workspace{ID: a.ID, Name: a.Name, Detail: a.Email}, a.ID == *current, i == *cursor) + if row.Focused() && !b.IsDisabled() { + *cursor = i + } + if row.Clicked() && !row.IsDisabled() && !b.IsDisabled() { + r.changed, *current, *open = a.ID != *current, a.ID, false + } + }).Height(280).MinHeight(0).AutoFocus() + restore() + internal.Ring(list, Tokens(c).Focus, theme.ControlRadius) + if list.Changed() && !b.IsDisabled() { + list.Focus() + } + if list.Submitted() && !b.IsDisabled() && *cursor >= 0 && *cursor < len(accounts) { + id := accounts[*cursor].ID + r.changed, *current, *open = id != *current, id, false + } + if Button(c, msg(c, "account_switcher.add"), ButtonOptions{Variant: Ghost}).Clicked() && !b.IsDisabled() { + r.add, *open = true, false + } + }) + if r.changed || r.add { + slog.Debug("mujica: account_switcher action", "changed", r.changed, "add", r.add) + } +} + +// accountTrigger displays an avatar, name and detail. +func accountTrigger(c *ui.Context, name, detail string, avatar *ui.Bitmap, disabled bool) *ui.Element { + k := Tokens(c) + d := Density(c) + b := ui.ButtonBase(c).Role(ui.RoleMenuButton).MinHeight(d.ControlHeight()+d.Unit()*2).Padding(d.Unit(), d.Unit()*3, d.Unit(), d.Unit()*2). + Gap(d.Unit() * 2).Radius(theme.ControlRadius).Justify(ui.Start).Disabled(disabled).Label(name).Transition(colorFade) + internal.Ring(b, k.Focus, theme.ControlRadius) + switch { + case b.Pressed(): + b.Background(k.Selection) + case b.Hovered(): + b.Background(k.SurfaceHover) + } + b.Children(func() { + Avatar(c, name, AvatarOptions{Image: avatar, Size: 28}) + ui.Column(c).Grow(1).MinWidth(0).Children(func() { + ui.Text(c, name).FontWeight(600).SingleLine().Ellipsis("…") + if detail != "" { + ui.Text(c, detail).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)).SingleLine().Ellipsis("…") + } + }) + ui.Icon(c, icons.Must("chevron-down")).Size(14, 14).TextColor(k.TextMuted) + }) + return b +} diff --git a/account_switcher_test.go b/account_switcher_test.go new file mode 100644 index 0000000..047032a --- /dev/null +++ b/account_switcher_test.go @@ -0,0 +1,139 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +var switcherAccounts = []Account{{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis", Email: "ob@mask"}} + +func TestAccountSwitcherSwitchesAndAdds(t *testing.T) { + cur, n, adds := "a", 0, 0 + disabled := false + tt := accountTester(500, 400, func(c *ui.Context) { + r := AccountSwitcher(c, &cur, switcherAccounts, AccountSwitcherOptions{Disabled: disabled}) + if r.Changed() { + n++ + } + if r.Add() { + adds++ + } + }) + if err := tt.Click("Sakiko Togawa"); err != nil { + t.Fatal(err) + } + if err := tt.Click("Oblivionis · ob@mask"); err != nil { + t.Fatal(err) + } + if cur != "b" || n != 1 { + t.Fatalf("switch: %q %d", cur, n) + } + tt.Key(0, ui.KeyDown) + if err := tt.Click("Add account…"); err != nil { + t.Fatal(err) + } + if adds != 1 || cur != "b" { + t.Fatal("add not reported") + } + disabled = true + tt.Frame() + _ = tt.Click("Oblivionis") + if tt.HasText("Add account…") { + t.Fatal("disabled switcher opened") + } + defer func() { + if recover() == nil { + t.Fatal("unknown current accepted") + } + }() + bad := "z" + accountTester(100, 100, func(c *ui.Context) { AccountSwitcher(c, &bad, switcherAccounts, AccountSwitcherOptions{}) }) +} + +func TestAccountSwitcherSnapshot(t *testing.T) { + snapshot(t, "account_switcher", 300, 90, func(c *ui.Context) { + cur := ui.Local(c.Root(), "cur", func() string { return "a" }) + ui.Column(c).Padding(16).AlignItems(ui.Start).Children(func() { AccountSwitcher(c, cur, switcherAccounts, AccountSwitcherOptions{}) }) + }) +} + +func TestAccountSwitcherLargeDataBuildsVisibleRows(t *testing.T) { + items := make([]Account, 100000) + for i := range items { + items[i] = Account{ID: fmt.Sprint(i), Name: fmt.Sprintf("Account %d", i)} + } + cur := "0" + tt := accountTester(500, 500, func(c *ui.Context) { AccountSwitcher(c, &cur, items, AccountSwitcherOptions{}) }) + if err := tt.Click("Account 0"); err != nil { + t.Fatal(err) + } + if n := len(tt.Texts()); n > 100 { + t.Fatalf("built %d text elements for a bounded viewport", n) + } + tt.Key(0, ui.KeyEnd) + tt.Key(0, ui.KeyEnter) + if cur != "99999" { + t.Fatalf("large-list keyboard choice %q", cur) + } +} + +func TestAccountSwitcherTabChoosesFocusedLargeRow(t *testing.T) { + items := make([]Account, 9) + for i := range items { + items[i] = Account{ID: fmt.Sprint(i), Name: fmt.Sprintf("Account %d", i)} + } + cur := "0" + tt := accountTester(500, 500, func(c *ui.Context) { AccountSwitcher(c, &cur, items, AccountSwitcherOptions{}) }) + if err := tt.Click("Account 0"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyTab) + if !tt.Focused("Account 1") { + t.Fatal("Tab did not reach the second account") + } + tt.Key(0, ui.KeyEnter) + if cur != "1" { + t.Fatal("Enter did not choose the focused account") + } +} + +func TestAccountSwitcherFocusFollowsReorderedID(t *testing.T) { + items := []Account{{ID: "a", Name: "A"}, {ID: "b", Name: "B"}, {ID: "c", Name: "C"}} + cur := "a" + tt := accountTester(500, 500, func(c *ui.Context) { AccountSwitcher(c, &cur, items, AccountSwitcherOptions{}) }) + if err := tt.Click("A"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyDown) + if !tt.Focused("B") { + t.Fatal("Down did not reach B") + } + items[1], items[2] = items[2], items[1] + tt.Frame() + tt.Key(0, ui.KeyEnter) + if cur != "b" { + t.Fatalf("reordered focus chose %q", cur) + } +} + +func TestAccountSwitcherArrowsContinueFromTabFocus(t *testing.T) { + items := make([]Account, 9) + for i := range items { + items[i] = Account{ID: fmt.Sprint(i), Name: fmt.Sprintf("Account %d", i)} + } + cur := "0" + tt := accountTester(500, 500, func(c *ui.Context) { AccountSwitcher(c, &cur, items, AccountSwitcherOptions{}) }) + if err := tt.Click("Account 0"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyEnter) + if cur != "2" { + t.Fatalf("arrow choice after Tab: %q", cur) + } +} diff --git a/api_key_manager.go b/api_key_manager.go new file mode 100644 index 0000000..01c35e1 --- /dev/null +++ b/api_key_manager.go @@ -0,0 +1,204 @@ +package mujica + +import ( + "fmt" + "log/slog" + "slices" + "strings" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// APIKey contains public metadata and the visible prefix. +type APIKey struct { + ID, Name, Prefix string + Created, LastUsed time.Time +} + +// ApiKeyManagerState holds creation, confirmation and one-time reveal state. +type ApiKeyManagerState struct { + creating, pending bool + name, secret string + err string + revoking string + keys []APIKey + index map[string]APIKey +} + +// Reveal presents the generated secret until dismissal. +func (s *ApiKeyManagerState) Reveal(secret string) { + if !s.pending || secret == "" { + panic("mujica: ApiKeyManagerState.Reveal needs a create in progress and a secret") + } + s.pending, s.secret, s.err = false, secret, "" + slog.Debug("mujica: api_key_manager revealed") +} + +// Fail displays a caller-provided creation error. +func (s *ApiKeyManagerState) Fail(err string) { + if !s.pending { + panic("mujica: ApiKeyManagerState.Fail needs a create in progress") + } + s.creating, s.pending, s.err = true, false, err +} + +// ApiKeyManagerOptions configure an ApiKeyManager. +type ApiKeyManagerOptions struct { + Disabled bool +} + +// ApiKeyManagerResult reports creation and revocation requests. +type ApiKeyManagerResult struct { + Element *ui.Element + create, revoke string +} + +// Create returns the requested name; finish with Reveal or Fail. +func (r ApiKeyManagerResult) Create() (string, bool) { return r.create, r.create != "" } + +// Revoked returns the confirmed key ID. +func (r ApiKeyManagerResult) Revoked() (string, bool) { return r.revoke, r.revoke != "" } + +// ApiKeyManager masks stored keys and confirms revocation. +func ApiKeyManager(c *ui.Context, s *ApiKeyManagerState, keys []APIKey, opts ApiKeyManagerOptions) ApiKeyManagerResult { + k := Tokens(c) + d := Density(c) + var r ApiKeyManagerResult + r.Element = ui.Column(c).Gap(d.Unit() * 3).MinWidth(460).Disabled(opts.Disabled) + disabled := r.Element.IsDisabled() + if disabled { + s.creating = s.creating && (s.pending || s.err != "") + s.revoking, s.secret = "", "" + } + if s.index == nil || !slices.Equal(s.keys, keys) { + s.keys, s.index = slices.Clone(keys), map[string]APIKey{} + for _, key := range keys { + if key.ID == "" || key.Name == "" || key.Prefix == "" { + panic("mujica: APIKey needs an ID, a Name and a Prefix") + } + if _, ok := s.index[key.ID]; ok { + panic("mujica: APIKey IDs must be unique") + } + s.index[key.ID] = key + } + } + byID := s.index + r.Element.Children(func() { + ui.Row(c).Children(func() { + if Button(c, msg(c, "api_key_manager.create"), ButtonOptions{Icon: icons.Must("plus"), Disabled: disabled}).Clicked() && !disabled { + s.creating, s.name, s.err = true, "", "" + } + }) + if len(keys) == 0 { + EmptyState(c, EmptyStateOptions{Title: msg(c, "api_key_manager.empty")}) + } + accountRows(c, len(keys), func(i int) any { return keys[i].ID }, func(i int) { + key := keys[i] + ui.Row(c).Key(key.ID).MinHeight(d.ControlHeight()+d.Unit()*4).Gap(d.Unit()*3).AlignItems(ui.Center). + BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).Children(func() { + ui.Column(c).Grow(1).MinWidth(0).Gap(2).Children(func() { + ui.Text(c, key.Name).FontWeight(600).SingleLine() + ui.Text(c, apiKeyMasked(key.Prefix)).Font(theme.MonoFont).TextColor(k.TextMuted).SingleLine() + ui.Text(c, apiKeyDates(c, key)).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)) + }) + b := Button(c, msg(c, "api_key_manager.revoke"), ButtonOptions{Variant: Secondary, Disabled: disabled}). + Label(fmt.Sprintf(msg(c, "api_key_manager.revoke_named"), key.Name)) + if b.Clicked() && !disabled { + s.revoking = key.ID + } + }) + }) + }) + if name, ok := apiKeyCreateDialog(c, s, disabled); ok && !disabled { + r.create = name + slog.Debug("mujica: api_key_manager create") + } + victim, exists := byID[s.revoking] + open := s.revoking != "" && exists + res := AlertDialog(c, &open, AlertDialogOptions{Title: fmt.Sprintf(msg(c, "api_key_manager.revoke_title"), victim.Name), + Description: msg(c, "api_key_manager.revoke_text"), ConfirmLabel: msg(c, "api_key_manager.revoke")}) + if res.Confirmed && !disabled { + r.revoke = victim.ID + slog.Debug("mujica: api_key_manager revoke") + } + if !open { + s.revoking = "" + } + return r +} + +// apiKeyMasked shows the prefix and hides the rest. +func apiKeyMasked(prefix string) string { return prefix + strings.Repeat("•", 12) } + +// apiKeyDates says when a key was made and last used. +func apiKeyDates(c *ui.Context, key APIKey) string { + used := msg(c, "api_key_manager.never") + if !key.LastUsed.IsZero() { + used = key.LastUsed.Format("2006-01-02") + } + return fmt.Sprintf(msg(c, "api_key_manager.dates"), key.Created.Format("2006-01-02"), used) +} + +// apiKeyCreateDialog collects a name and reveals the generated secret. +func apiKeyCreateDialog(c *ui.Context, s *ApiKeyManagerState, disabled bool) (string, bool) { + k := Tokens(c) + d := Density(c) + open := !disabled && (s.creating || s.pending || s.secret != "") + was := open + asked := false + title := msg(c, "api_key_manager.create_title") + if s.secret != "" { + title = msg(c, "api_key_manager.secret_title") + } + Dialog(c, &open, DialogOptions{Title: title, KeepOnBackdrop: true, KeepOnEscape: s.pending, HideClose: s.pending, Actions: func() { + switch { + case s.secret != "": + if Button(c, msg(c, "api_key_manager.done"), ButtonOptions{}).Clicked() { + open = false + } + default: + if Button(c, msg(c, "api_key_manager.cancel"), ButtonOptions{Variant: Secondary, Disabled: s.pending}).Clicked() { + open = false + } + ok := strings.TrimSpace(s.name) != "" + if Button(c, msg(c, "api_key_manager.create"), ButtonOptions{Loading: s.pending, Disabled: !ok}).Clicked() && ok && !s.pending { + asked = true + } + } + }}, func() { + if s.secret != "" { + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Start).Padding(d.Unit() * 3).Radius(theme.ControlRadius).Background(k.Warning.Alpha(0.12)).Children(func() { + ui.Icon(c, icons.Must("triangle-alert")).Size(16, 16).TextColor(k.Warning).Shrink(0) + ui.Text(c, msg(c, "api_key_manager.once")).Grow(1) + }) + ui.Row(c).Gap(d.Unit()*2).AlignItems(ui.Center).Padding(d.Unit()*2, d.Unit()*3).Radius(theme.ControlRadius). + Background(k.Background).Border(theme.BorderWidth, k.Border).Children(func() { + ui.Text(c, s.secret).Font(theme.MonoFont).Selectable().Label(msg(c, "api_key_manager.secret_title")).Grow(1).MinWidth(0) + if iconAction(c, icons.Must("copy"), msg(c, "api_key_manager.copy")).Clicked() { + c.WriteClipboard(s.secret) + c.Announce(msg(c, "api_key_manager.copied")) + } + }) + return + } + res := TextInput(c, &s.name, TextInputOptions{Label: msg(c, "api_key_manager.name"), Placeholder: msg(c, "api_key_manager.name"), + Disabled: s.pending, Error: s.err}) + res.Input.AutoFocus() + if res.Submitted() && strings.TrimSpace(s.name) != "" && !s.pending { + asked = true + } + }) + if was && !open && !s.pending { + // Dismissing wipes the one-time secret. + s.creating, s.secret, s.name = false, "", "" + } + if asked { + s.pending, s.err = true, "" + return strings.TrimSpace(s.name), true + } + return "", false +} diff --git a/api_key_manager_test.go b/api_key_manager_test.go new file mode 100644 index 0000000..3b58882 --- /dev/null +++ b/api_key_manager_test.go @@ -0,0 +1,201 @@ +package mujica + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +var apiKeys = []APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c", Created: time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)}} + +func TestApiKeyManagerCreateRevealOnceAndRevoke(t *testing.T) { + logs := accountLogs(t) + const secret = "mk_live_9f2c_SECRET_DO_NOT_LOG" + var s ApiKeyManagerState + var created, revoked []string + tt := accountTester(700, 600, func(c *ui.Context) { + r := ApiKeyManager(c, &s, apiKeys, ApiKeyManagerOptions{}) + if name, ok := r.Create(); ok { + created = append(created, name) + } + if id, ok := r.Revoked(); ok { + revoked = append(revoked, id) + } + }) + if !tt.HasText("mk_live_9f2c••••••••••••") || !tt.HasText("Created 2026-09-01 · last used never") { + t.Fatalf("masked list: %q", tt.Texts()) + } + _ = tt.Click("Create key") + tt.Type("CI") + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeyEnter) + if len(created) != 1 || created[0] != "CI" { + t.Fatalf("created %v", created) + } + s.Reveal(secret) + tt.Frame() + if s.secret != secret || !tt.HasText("Copy key") { + t.Fatal("new key not shown") + } + if err := tt.Click("Copy key"); err != nil { + t.Fatal(err) + } + if tt.Clipboard() != secret { + t.Fatal("copy did not take the key") + } + _ = tt.Click("I've saved it") + if tt.HasText("Copy key") || s.secret != "" { + t.Fatal("key still shown or kept after dismissal") + } + if strings.Contains(logs.String(), secret) { + t.Fatal("the key reached the log") + } + _ = tt.Click("Revoke Deploy bot") + if !tt.Focused("Cancel") { + t.Fatal("revoke confirmation not focused on Cancel") + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if len(revoked) != 1 || revoked[0] != "k1" { + t.Fatalf("revoked %v", revoked) + } +} + +func TestApiKeyManagerFailAndDisabled(t *testing.T) { + var s ApiKeyManagerState + disabled, made := false, 0 + tt := accountTester(700, 600, func(c *ui.Context) { + if _, ok := ApiKeyManager(c, &s, nil, ApiKeyManagerOptions{Disabled: disabled}).Create(); ok { + made++ + } + }) + if !tt.HasText("No API keys") { + t.Fatal("no empty state") + } + _ = tt.Click("Create key") + tt.Type("x") + tt.Key(0, ui.KeyEnter) + tt.Key(0, ui.KeyEscape) + if made != 1 || !s.pending { + t.Fatal("escape left a pending create") + } + s.Fail("Key limit reached.") + tt.Frame() + if !tt.HasText("Key limit reached.") { + t.Fatalf("failure: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEscape) + disabled = true + tt.Frame() + _ = tt.Click("Create key") + if tt.HasText("New API key") { + t.Fatal("disabled manager opened the dialog") + } +} + +func TestApiKeyManagerSnapshot(t *testing.T) { + snapshot(t, "api_key_manager", 560, 220, func(c *ui.Context) { + s := ui.Local(c.Root(), "s", func() ApiKeyManagerState { return ApiKeyManagerState{} }) + ui.Column(c).Padding(16).Children(func() { ApiKeyManager(c, s, apiKeys, ApiKeyManagerOptions{}) }) + }) +} + +func TestApiKeyManagerDisablingClosesEveryDialog(t *testing.T) { + for _, phase := range []string{"name", "pending", "secret", "revoke"} { + t.Run(phase, func(t *testing.T) { + var s ApiKeyManagerState + disabled := false + tt := accountTester(700, 600, func(c *ui.Context) { + ui.Column(c).Disabled(disabled).Children(func() { ApiKeyManager(c, &s, apiKeys, ApiKeyManagerOptions{}) }) + }) + if phase == "revoke" { + if err := tt.Click("Revoke Deploy bot"); err != nil { + t.Fatal(err) + } + } else { + if err := tt.Click("Create key"); err != nil { + t.Fatal(err) + } + if phase != "name" { + tt.Type("CI") + tt.Key(0, ui.KeyEnter) + } + if phase == "secret" { + s.Reveal("one-time-test-secret") + tt.Frame() + } + } + disabled = true + tt.Frame() + if tt.HasText("New API key") || tt.HasText("Copy your new key") || tt.HasText("Revoke Deploy bot?") || s.secret != "" { + t.Fatal("disabled manager retains an active dialog or secret") + } + }) + } +} + +func TestApiKeyManagerRemovedKeyCancelsConfirmation(t *testing.T) { + var s ApiKeyManagerState + keys := apiKeys + tt := accountTester(700, 600, func(c *ui.Context) { ApiKeyManager(c, &s, keys, ApiKeyManagerOptions{}) }) + if err := tt.Click("Revoke Deploy bot"); err != nil { + t.Fatal(err) + } + keys = nil + tt.Frame() + if s.revoking != "" || tt.HasText("Apps using this key stop working at once.") { + t.Fatal("removed key keeps confirmation open") + } +} + +func TestApiKeyManagerLargeDataBuildsVisibleRows(t *testing.T) { + items := make([]APIKey, 100000) + for i := range items { + items[i] = APIKey{ID: fmt.Sprint(i), Name: fmt.Sprintf("Key %d", i), Prefix: "public"} + } + var s ApiKeyManagerState + tt := accountTester(600, 500, func(c *ui.Context) { ApiKeyManager(c, &s, items, ApiKeyManagerOptions{}) }) + if n := len(tt.Texts()); n > 100 { + t.Fatalf("built %d text elements for a bounded viewport", n) + } +} + +func TestApiKeyManagerFailureSurvivesDisabledRequest(t *testing.T) { + for _, whileDisabled := range []bool{false, true} { + t.Run(fmt.Sprint(whileDisabled), func(t *testing.T) { + var s ApiKeyManagerState + disabled, requests := false, 0 + tt := accountTester(700, 600, func(c *ui.Context) { + if _, ok := ApiKeyManager(c, &s, nil, ApiKeyManagerOptions{Disabled: disabled}).Create(); ok { + requests++ + } + }) + if err := tt.Click("Create key"); err != nil { + t.Fatal(err) + } + tt.Type("CI") + tt.Key(0, ui.KeyEnter) + disabled = true + tt.Frame() + if whileDisabled { + s.Fail("Create failed") + tt.Frame() + } + disabled = false + if !whileDisabled { + s.Fail("Create failed") + } + tt.Frame() + if !tt.HasText("Create failed") || s.name != "CI" || s.pending { + t.Fatal("disabled request lost its failure or draft") + } + tt.Key(0, ui.KeyEnter) + if requests != 2 || !s.pending { + t.Fatal("failed request cannot be retried") + } + }) + } +} diff --git a/coachmark.go b/coachmark.go new file mode 100644 index 0000000..f436519 --- /dev/null +++ b/coachmark.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// CoachmarkStep uses an anchor built in the current frame. +type CoachmarkStep struct { + Anchor *ui.Element + Title, Body string +} + +// CoachmarkResult reports tour completion or dismissal. +type CoachmarkResult struct { + Element *ui.Element + finished, dismissed bool +} + +// Finished reports Done on the last step. +func (r CoachmarkResult) Finished() bool { return r.finished } + +// Dismissed reports Escape, a click outside or Skip. +func (r CoachmarkResult) Dismissed() bool { return r.dismissed } + +// Coachmark provides anchored tips and keyboard-accessible step navigation. +func Coachmark(c *ui.Context, open *bool, step *int, steps []CoachmarkStep) CoachmarkResult { + if len(steps) == 0 || *step < 0 || *step >= len(steps) { + panic(fmt.Sprintf("mujica: Coachmark step %d outside %d steps", *step, len(steps))) + } + var r CoachmarkResult + if !*open { + return r + } + cur := steps[*step] + if cur.Anchor == nil || cur.Title == "" { + panic("mujica: CoachmarkStep needs an Anchor and a Title") + } + if cur.Anchor.IsDisabled() { + *open = false + return r + } + k := Tokens(c) + d := Density(c) + if b := cur.Anchor.Bounds(); b.W > 0 { + const pad = 4 + ui.Overlay(c, func() { + ui.Box(c).Absolute().Left(b.X-pad).Top(b.Y-pad).Size(b.W+2*pad, b.H+2*pad).PassThrough().Role(ui.RoleNone). + Border(2, k.AccentText).Radius(theme.CardRadius) + }) + } + from := *step + r.Element = Popover(c, cur.Anchor, open, PopoverOptions{Title: cur.Title, Width: 300}, func() { + ui.Column(c).Gap(d.Unit() * 3).Children(func() { + if cur.Body != "" { + ui.Text(c, cur.Body) + } + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() { + ui.Text(c, fmt.Sprintf(msg(c, "coachmark.count"), *step+1, len(steps))).TextColor(k.TextMuted). + FontSize(FontSize(c, theme.CaptionSize)).Grow(1) + if *step < len(steps)-1 && Button(c, msg(c, "coachmark.skip"), ButtonOptions{Variant: Ghost}).Clicked() { + *open, r.dismissed = false, true + } + if *step > 0 && Button(c, msg(c, "coachmark.back"), ButtonOptions{Variant: Secondary}).Clicked() { + *step-- + } + last := *step == len(steps)-1 + next := msg(c, "coachmark.next") + if last { + next = msg(c, "coachmark.done") + } + if Button(c, next, ButtonOptions{}).AutoFocus().Clicked() { + if last { + *open, r.finished = false, true + } else { + *step++ + } + } + }) + }) + }) + if !*open && !r.finished { + r.dismissed = true + } + if *step != from || !*open { + slog.Debug("mujica: coachmark step", "step", *step, "open", *open) + } + return r +} diff --git a/coachmark_test.go b/coachmark_test.go new file mode 100644 index 0000000..648bd15 --- /dev/null +++ b/coachmark_test.go @@ -0,0 +1,79 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func coachmarkView(c *ui.Context, open *bool, step *int, r *CoachmarkResult) { + var a, b *ui.Element + ui.Row(c).Gap(40).Padding(20).Children(func() { + a = Button(c, "Compose", ButtonOptions{}) + b = Button(c, "Archive", ButtonOptions{Variant: Secondary}) + }) + *r = Coachmark(c, open, step, []CoachmarkStep{ + {Anchor: a, Title: "Write here", Body: "Start a new letter."}, + {Anchor: b, Title: "Keep it tidy", Body: "Archive finished threads."}, + }) +} + +func TestCoachmarkStepsAndEscape(t *testing.T) { + open, step := true, 0 + var r CoachmarkResult + done, dismissed := 0, 0 + tt := accountTester(600, 400, func(c *ui.Context) { + coachmarkView(c, &open, &step, &r) + if r.Finished() { + done++ + } + if r.Dismissed() { + dismissed++ + } + }) + tt.Frame() + if !tt.HasText("Write here") || !tt.HasText("1 of 2") { + t.Fatalf("first: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + if step != 1 || !tt.HasText("Keep it tidy") { + t.Fatalf("next: %d", step) + } + _ = tt.Click("Done") + if done != 1 || open { + t.Fatal("done not reported") + } + open, step = true, 0 + tt.Frame() + tt.Key(0, ui.KeyEscape) + if open || dismissed != 1 || done != 1 { + t.Fatalf("escape: open %v dismissed %d", open, dismissed) + } +} + +func TestCoachmarkSnapshot(t *testing.T) { + snapshot(t, "coachmark", 420, 230, func(c *ui.Context) { + open := ui.Local(c.Root(), "open", func() bool { return true }) + step := ui.Local(c.Root(), "step", func() int { return 0 }) + var r CoachmarkResult + coachmarkView(c, open, step, &r) + }) +} + +func TestCoachmarkDisabledAnchorCloses(t *testing.T) { + open, step, disabled := true, 0, false + tt := accountTester(600, 400, func(c *ui.Context) { + a := Button(c, "Compose", ButtonOptions{Disabled: disabled}) + Coachmark(c, &open, &step, []CoachmarkStep{{Anchor: a, Title: "Write here"}}) + }) + disabled = true + tt.Frame() + if open || tt.HasText("Write here") { + t.Fatal("disabled anchor retains coachmark") + } +} + +func TestCoachmarkInvalidStep(t *testing.T) { + open, step := true, 1 + accountPanics(t, func(c *ui.Context) { Coachmark(c, &open, &step, []CoachmarkStep{{Title: "Only step"}}) }) +} diff --git a/docs/components/461-settings-layout.md b/docs/components/461-settings-layout.md new file mode 100644 index 0000000..295bee6 --- /dev/null +++ b/docs/components/461-settings-layout.md @@ -0,0 +1,53 @@ +# 461 SettingsLayout + +设置布局。 + +## 用途 + +设置页框架:左侧分区导航(Sidebar),右侧显示当前分区;配套 `SettingsSection` 分节与 `SettingsRow` 设置行(标签、说明、控件)。 + +## 最小示例 + +```go +page := "" +sound := true +pages := []mujica.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.SettingsLayout(c, &page, pages, mujica.SettingsLayoutOptions{}, func(id string) { + mujica.SettingsSection(c, "Notifications", func() { + mujica.SettingsRow(c, "Sound", mujica.SettingsRowOptions{ID: "sound"}, func() *ui.Element { + return mujica.Switch(c, &sound, "", mujica.SwitchOptions{}) + }) + }) + }) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `page` | `*string` | 必填 | 当前分区 ID;空值取第一个,未知 ID panic。 | +| `pages` | `[]SettingsPage` | 必填 | ID、Label、Icon。 | +| `Title` | `string` | 内置“Settings” | 导航标题。 | +| `content` | `func(id string)` | 必填 | 构建分区内容。 | +| `SettingsRow control` | `func() *ui.Element` | 必填 | 返回控件;行标签与说明关联到它。 | +| `SettingsRowOptions.ID` | `string` | "" | 供 SettingsSearch 跳转定位。 | + +## 状态 + +导航选中、行悬停、被搜索定位的行短暂高亮(减少动画时不高亮)。 + +## 事件 + +`ChoiceResult.Changed()` 在切换分区时为 true。 + +## 键盘操作 + +导航沿用 Sidebar 键盘操作;Tab 进入内容区各控件;点击行文字聚焦其控件。 + +## 限制 + +- 内容区独立滚动,最大宽度 720 DIP。 diff --git a/docs/components/462-settings-search.md b/docs/components/462-settings-search.md new file mode 100644 index 0000000..3a87a89 --- /dev/null +++ b/docs/components/462-settings-search.md @@ -0,0 +1,48 @@ +# 462 SettingsSearch + +设置搜索。 + +## 用途 + +按标签、说明与关键词搜索设置项,高亮匹配片段;选中后切到对应分区并把该设置行滚入视野、聚焦其控件。 + +## 最小示例 + +```go +query, page := "", "general" +entries := []mujica.SettingEntry{{ID: "sound", Page: "general", Label: "Sound", Keywords: []string{"audio"}}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if e, ok := mujica.SettingsSearch(c, &query, entries, mujica.SettingsSearchOptions{}).Chosen(); ok { + page = e.Page + } + fmt.Println("page", page) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `query` | `*string` | 必填 | 搜索文字。 | +| `entries` | `[]SettingEntry` | 必填 | ID(对应 SettingsRow ID)、Page、Label、Description、Keywords。 | +| `Limit` | `int` | 8 | 最多显示条数。 | + +## 状态 + +无输入、有结果(当前项底色)、无结果。 + +## 事件 + +`SettingsSearchResult.Chosen()` 返回选中的条目;调用方据此设置分区。 + +## 键盘操作 + +在搜索框中 ↑/↓ 移动当前项,Enter 跳转;也可点击结果。 + +## 限制 + +大集合使用虚拟列表,构建当前可见行;搜索与分组在数据或查询变化时更新。 + +- 匹配不区分大小写,按子串查找。 diff --git a/docs/components/463-theme-selector.md b/docs/components/463-theme-selector.md new file mode 100644 index 0000000..f5531f2 --- /dev/null +++ b/docs/components/463-theme-selector.md @@ -0,0 +1,41 @@ +# 463 ThemeSelector + +主题选择。 + +## 用途 + +以三张预览卡选择跟随系统、浅色或深色;预览用对应主题的设计变量绘制。 + +## 最小示例 + +```go +mode := mujica.System +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{Mode: mode}) + mujica.ThemeSelector(c, &mode, mujica.ThemeSelectorOptions{}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `mode` | `*Mode` | 必填 | System、Light 或 Dark;其他值 panic。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +默认、悬停、聚焦、选中(选中底色、边框与勾选)、禁用。 + +## 事件 + +`ChoiceResult.Changed()`。 + +## 键盘操作 + +Tab 进入单选组,←/→ 移动,Space 选择。 + +## 限制 + +- 无。 diff --git a/docs/components/464-accent-color-picker.md b/docs/components/464-accent-color-picker.md new file mode 100644 index 0000000..1a94706 --- /dev/null +++ b/docs/components/464-accent-color-picker.md @@ -0,0 +1,42 @@ +# 464 AccentColorPicker + +主色选择。 + +## 用途 + +从候选色中选择主色;承载浅色文字对比度不足 4.5:1 的候选带警示标记,选中时给出提示。 + +## 最小示例 + +```go +accent := ui.Hex("#7D2034") +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.AccentColorPicker(c, &accent, mujica.AccentColorPickerOptions{}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `color` | `*ui.Color` | 必填 | 当前主色。 | +| `Candidates` | `[]ui.Color` | 内置 8 色 | 候选色。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +默认、选中(外框与勾选)、对比度不足(警示图标与说明)、禁用。 + +## 事件 + +`ChoiceResult.Changed()`。 + +## 键盘操作 + +Tab 进入单选组,←/→ 移动,Space 选择。 + +## 限制 + +- 对比度按 OnAccent 文字计算;只标记,不阻止选择。 diff --git a/docs/components/465-shortcut-recorder.md b/docs/components/465-shortcut-recorder.md new file mode 100644 index 0000000..1824e88 --- /dev/null +++ b/docs/components/465-shortcut-recorder.md @@ -0,0 +1,46 @@ +# 465 ShortcutRecorder + +快捷键录制。 + +## 用途 + +点击后录制下一次按下的组合键;与已占用的快捷键冲突时提示并保留原值;按平台显示修饰键。 + +## 最小示例 + +```go +palette := mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ShortcutRecorder(c, &palette, mujica.ShortcutRecorderOptions{ + Label: "Command palette", + Taken: []mujica.ShortcutBinding{{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}}, + }) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `value` | `*Shortcut` | 必填 | Mods 与 Key;零值表示未设置。 | +| `Label` | `string` | 必填 | 动作名称。 | +| `Taken` | `[]ShortcutBinding` | nil | 已占用的快捷键。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +显示、录制中、冲突提示、缺少修饰键提示、禁用。 + +## 事件 + +`ChoiceResult.Changed()` 在录到新快捷键时为 true。 + +## 键盘操作 + +Enter/Space 开始录制;Escape 取消;Tab 离开。字母、数字等须带 Ctrl、Alt 或 Command,F1–F12 可单独使用。 + +## 限制 + +- 系统或应用菜单先拦截的组合键(如 Cmd+Q)无法录制。 diff --git a/docs/components/466-keyboard-shortcuts-list.md b/docs/components/466-keyboard-shortcuts-list.md new file mode 100644 index 0000000..03d299b --- /dev/null +++ b/docs/components/466-keyboard-shortcuts-list.md @@ -0,0 +1,47 @@ +# 466 KeyboardShortcutsList + +快捷键列表。 + +## 用途 + +分组显示快捷键表,可按动作名或按键搜索。 + +## 最小示例 + +```go +query := "" +groups := []mujica.ShortcutGroup{{Title: "General", Bindings: []mujica.ShortcutBinding{ + {Action: "Command palette", Shortcut: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}}, +}}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.KeyboardShortcutsList(c, &query, groups, mujica.KeyboardShortcutsListOptions{}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `query` | `*string` | 必填 | 搜索文字。 | +| `groups` | `[]ShortcutGroup` | 必填 | Title 与 Bindings。 | +| `Label` | `string` | 内置“Search shortcuts” | 搜索框名称。 | + +## 状态 + +全部、筛选、无结果。 + +## 事件 + +无。 + +## 键盘操作 + +Tab 进入搜索框;每行读出动作与按键。 + +## 限制 + +大集合使用虚拟列表,构建当前可见行;搜索与分组在数据或查询变化时更新。 + +- 按键按平台显示,macOS 使用符号。 diff --git a/docs/components/467-login-form.md b/docs/components/467-login-form.md new file mode 100644 index 0000000..7747215 --- /dev/null +++ b/docs/components/467-login-form.md @@ -0,0 +1,47 @@ +# 467 LoginForm + +登录表单。 + +## 用途 + +账号、密码与“记住我”;提交中禁用输入;显示调用方给出的错误(含频率限制、锁定)。密码在私有临时状态中编辑,经提交回调传出,完成后清除;账号校验由调用方完成。 + +## 最小示例 + +```go +var state mujica.LoginFormState +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.LoginForm(c, &state, mujica.LoginFormOptions{OnSubmit: func(account, password string, remember bool) { + // Submit credentials through the caller-provided auth service. + state.EndSubmit("") + }}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `s` | `*LoginFormState` | 必填 | Account、Remember 可读写;密码不可读取。 | +| `OnSubmit` | `func(account, password string, remember bool)` | 必填 | 唯一接收凭据的出口。 | +| `AccountLabel` | `string` | 内置“Email or username” | 账号标签。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +默认、必填错误、提交中、失败(错误文字)、禁用。 + +## 事件 + +`OnSubmit` 每次提交调用一次;完成后调用 `s.EndSubmit(err)`,密码随即清除。 + +## 键盘操作 + +Tab 顺序:账号、密码、显示密码、记住我、登录;任一字段中 Enter 提交;缺项时聚焦第一个空字段。 + +## 限制 + +- 凭据只经调用方回调传出;密码、验证码与密钥不会写入日志、Label 或 Description。 +- 提交失败后需重新输入密码。 diff --git a/docs/components/468-oauth-buttons.md b/docs/components/468-oauth-buttons.md new file mode 100644 index 0000000..12df8ea --- /dev/null +++ b/docs/components/468-oauth-buttons.md @@ -0,0 +1,44 @@ +# 468 OAuthButtons + +第三方登录按钮。 + +## 用途 + +按调用方提供的名称显示“使用 X 继续”按钮组;不使用商标图形。 + +## 最小示例 + +```go +providers := []mujica.OAuthProvider{{ID: "sso", Name: "Company SSO"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if id, ok := mujica.OAuthButtons(c, providers, mujica.OAuthButtonsOptions{}).Chosen(); ok { + fmt.Println("start sign-in with", id) + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `providers` | `[]OAuthProvider` | 必填 | ID、Name 必填;Icon 可选,应为非商标图形。 | +| `Pending` | `string` | "" | 正在进行的提供方:显示加载,其余禁用。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +默认、进行中、禁用。 + +## 事件 + +`OAuthButtonsResult.Chosen()` 返回提供方 ID。 + +## 键盘操作 + +Tab 在按钮间移动,Enter/Space 选择。 + +## 限制 + +- 登录流程由调用方完成。 diff --git a/docs/components/469-two-factor-input.md b/docs/components/469-two-factor-input.md new file mode 100644 index 0000000..4c0db44 --- /dev/null +++ b/docs/components/469-two-factor-input.md @@ -0,0 +1,48 @@ +# 469 TwoFactorInput + +两步验证码。 + +## 用途 + +六位验证码输入(基于 PinInput),支持整段粘贴;重新发送带倒计时;显示调用方错误。 + +## 最小示例 + +```go +code := "" +resendAt := time.Now().Add(30 * time.Second) +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + r := mujica.TwoFactorInput(c, &code, mujica.TwoFactorInputOptions{ResendAt: resendAt}) + if r.Completed() { + fmt.Println("verify the code") + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `code` | `*string` | 必填 | 验证码数字。 | +| `ResendAt` | `time.Time` | 零值 | 此时间前重新发送按钮倒计时并禁用。 | +| `Error` | `string` | "" | 调用方错误,如过期。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +输入中、完成、错误、倒计时、可重发、禁用。 + +## 事件 + +`TwoFactorResult.Completed()` 在填满第六位时;`Resend()` 请求重发。 + +## 键盘操作 + +数字键填格,Backspace 删除,Cmd/Ctrl+V 粘贴;Tab 到重新发送按钮。 + +## 限制 + +- 验证码与密钥通过调用方状态接入;组件事件与日志仅包含完成或失败标记。 +- 倒计时每秒刷新一次,结束后不再重绘。 diff --git a/docs/components/470-profile-editor.md b/docs/components/470-profile-editor.md new file mode 100644 index 0000000..f29d295 --- /dev/null +++ b/docs/components/470-profile-editor.md @@ -0,0 +1,48 @@ +# 470 ProfileEditor + +个人资料编辑。 + +## 用途 + +头像、名称与简介;与已保存版本不同时显示未保存标记;保存、放弃与更换头像由调用方处理。 + +## 最小示例 + +```go +saved := mujica.Profile{Name: "Sakiko", Bio: "Keyboard."} +profile := saved +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.ProfileEditor(c, &profile, mujica.ProfileEditorOptions{Saved: saved}).Save() { + saved = profile + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `p` | `*Profile` | 必填 | Name、Bio。 | +| `Saved` | `Profile` | 零值 | 已保存的资料。 | +| `Avatar` | `*ui.Bitmap` | nil | 头像;nil 显示首字母。 | +| `BioLimit` | `int` | 160 | 可保存的简介字数上限;超限保留输入并阻止保存。 | +| `Saving` | `bool` | false | 保存中,阻止编辑。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +已保存、未保存(菱形与文字)、名称为空、简介超限、保存中、禁用。 + +## 事件 + +`Save()`、`ChangeAvatar()`、`Discarded()`。 + +## 键盘操作 + +Tab 依次经过更换头像、名称、简介、放弃、保存。 + +## 限制 + +- 头像选择与上传由调用方完成,可配合 FilePicker。 diff --git a/docs/components/471-account-switcher.md b/docs/components/471-account-switcher.md new file mode 100644 index 0000000..a680d88 --- /dev/null +++ b/docs/components/471-account-switcher.md @@ -0,0 +1,47 @@ +# 471 AccountSwitcher + +账户切换。 + +## 用途 + +显示当前账户,菜单中切换已登录账户或添加账户。 + +## 最小示例 + +```go +current := "a" +accounts := []mujica.Account{{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.AccountSwitcher(c, ¤t, accounts, mujica.AccountSwitcherOptions{}).Add() { + fmt.Println("sign in another account") + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `current` | `*string` | 必填 | 当前账户 ID,须在列表中。 | +| `accounts` | `[]Account` | 必填 | ID、Name 必填;Email、Avatar 可选。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +关闭、展开、当前账户勾选、禁用。 + +## 事件 + +`Changed()` 切换;`Add()` 请求添加账户。 + +## 键盘操作 + +Enter/Space/↓ 打开菜单,方向键移动,Enter 选择,Escape 关闭。 + +## 限制 + +大集合使用虚拟列表,仅构建当前可见行。 + +- 菜单项只显示文字,头像只在按钮上。 diff --git a/docs/components/472-workspace-switcher.md b/docs/components/472-workspace-switcher.md new file mode 100644 index 0000000..f7bbb97 --- /dev/null +++ b/docs/components/472-workspace-switcher.md @@ -0,0 +1,45 @@ +# 472 WorkspaceSwitcher + +工作区切换。 + +## 用途 + +显示当前工作区,弹层中搜索并切换。 + +## 最小示例 + +```go +current := "ave" +spaces := []mujica.Workspace{{ID: "ave", Name: "Ave Mujica"}, {ID: "mygo", Name: "MyGO!!!!!"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.WorkspaceSwitcher(c, ¤t, spaces, mujica.WorkspaceSwitcherOptions{}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `current` | `*string` | 必填 | 当前工作区 ID。 | +| `spaces` | `[]Workspace` | 必填 | ID、Name 必填,Detail 可选。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +关闭、展开、搜索、无结果、当前项勾选、禁用。 + +## 事件 + +`ChoiceResult.Changed()`。 + +## 键盘操作 + +打开后焦点在搜索框;↑/↓ 移动,Enter 切换,Escape 关闭。 + +## 限制 + +大集合使用虚拟列表,构建当前可见行;搜索与分组在数据或查询变化时更新。 + +- 无。 diff --git a/docs/components/473-user-menu.md b/docs/components/473-user-menu.md new file mode 100644 index 0000000..8737e75 --- /dev/null +++ b/docs/components/473-user-menu.md @@ -0,0 +1,46 @@ +# 473 UserMenu + +用户菜单。 + +## 用途 + +头像按钮打开菜单:在线状态、设置与退出登录。头像角标以形状与颜色共同表示状态。 + +## 最小示例 + +```go +status := mujica.PresenceOnline +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.UserMenu(c, &status, mujica.UserMenuOptions{Name: "Sakiko Togawa"}).SignOut() { + fmt.Println("sign out") + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `status` | `*UserStatus` | 必填 | PresenceOnline、PresenceAway、PresenceBusy、PresenceInvisible。 | +| `Name` | `string` | 必填 | 用户名。 | +| `Email` | `string` | "" | 邮箱。 | +| `Avatar` | `*ui.Bitmap` | nil | 头像。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +关闭、展开、四种状态、禁用。 + +## 事件 + +`StatusChanged()`、`Settings()`、`SignOut()`。 + +## 键盘操作 + +Enter/Space/↓ 打开,方向键移动,Enter 选择,Escape 关闭。 + +## 限制 + +- 按钮名称包含用户名与状态。 diff --git a/docs/components/474-session-list.md b/docs/components/474-session-list.md new file mode 100644 index 0000000..dc7769b --- /dev/null +++ b/docs/components/474-session-list.md @@ -0,0 +1,45 @@ +# 474 SessionList + +登录会话。 + +## 用途 + +列出已登录设备、位置与最近活跃时间;可注销单个会话或经确认注销其他全部会话。 + +## 最小示例 + +```go +sessions := []mujica.Session{{ID: "s1", Device: "MacBook Pro", Current: true}, {ID: "s2", Device: "iPhone", Location: "Osaka"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if id, ok := mujica.SessionList(c, sessions, mujica.SessionListOptions{}).Revoked(); ok { + fmt.Println("sign out", id) + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `sessions` | `[]Session` | 必填 | ID、Device 必填;不包含会话令牌。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +本设备标记、最近活跃未知、确认对话框、禁用。 + +## 事件 + +`Revoked()` 返回会话 ID;`OthersRevoked()` 在确认后为 true。 + +## 键盘操作 + +Tab 到各注销按钮;确认框默认聚焦取消。 + +## 限制 + +大集合使用虚拟列表,仅构建当前可见行。 + +- 注销由调用方执行。 diff --git a/docs/components/475-api-key-manager.md b/docs/components/475-api-key-manager.md new file mode 100644 index 0000000..7593dbc --- /dev/null +++ b/docs/components/475-api-key-manager.md @@ -0,0 +1,51 @@ +# 475 ApiKeyManager + +API 密钥管理。 + +## 用途 + +密钥列表只显示前缀与掩码;创建后明文只显示一次并可复制,关闭即从内存清除;撤销需确认。 + +## 最小示例 + +```go +var state mujica.ApiKeyManagerState +keys := []mujica.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if name, ok := mujica.ApiKeyManager(c, &state, keys, mujica.ApiKeyManagerOptions{}).Create(); ok { + fmt.Println("create", name) + state.Reveal("secret from your key service") + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `s` | `*ApiKeyManagerState` | 必填 | 对话框与一次性明文。 | +| `keys` | `[]APIKey` | 必填 | ID、Name、Prefix 必填;不含明文。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +列表、空列表、输入名称、创建中、显示明文、失败、撤销确认、禁用。 + +## 事件 + +`Create()` 返回新密钥名称:调用方生成后调用 `s.Reveal(secret)` 或 `s.Fail(msg)`;`Revoked()` 返回确认撤销的 ID。 + +## 键盘操作 + +Tab 到创建与撤销按钮;对话框中 Enter 创建;复制按钮写入剪贴板并播报“已复制”。 + +## 限制 + +大集合使用虚拟列表,仅构建当前可见行。 + +- 验证码与密钥通过调用方状态接入;组件事件与日志仅包含完成或失败标记。 +- 明文作为可选择文字显示一次,屏幕阅读器可读出文字本身。 +- 创建进行中等待调用方结果;禁用关闭全部弹层并清除已显示明文。 +- 待处理请求跨禁用保留;失败后恢复名称与错误,支持重试。 diff --git a/docs/components/476-usage-quota.md b/docs/components/476-usage-quota.md new file mode 100644 index 0000000..49b33cf --- /dev/null +++ b/docs/components/476-usage-quota.md @@ -0,0 +1,42 @@ +# 476 UsageQuota + +用量配额。 + +## 用途 + +每项资源的用量与上限进度条;接近与超出上限以图标与文字提示;显示重置时间。 + +## 最小示例 + +```go +items := []mujica.QuotaItem{{Label: "API calls", Used: 4200, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.UsageQuota(c, items, mujica.UsageQuotaOptions{ResetAt: time.Now().Add(72 * time.Hour)}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `items` | `[]QuotaItem` | 必填 | Label、Used、Limit(>0)、Unit。 | +| `ResetAt` | `time.Time` | 零值 | 重置时间;一天内显示小时,否则显示日期。 | +| `Warn` | `float64` | 0.8 | 达到该比例即提示接近上限。 | + +## 状态 + +正常、接近上限、已达上限。 + +## 事件 + +无。 + +## 键盘操作 + +不获取焦点;每项以计量角色读出。 + +## 限制 + +- 无。 diff --git a/docs/components/477-onboarding-wizard.md b/docs/components/477-onboarding-wizard.md new file mode 100644 index 0000000..562cf5b --- /dev/null +++ b/docs/components/477-onboarding-wizard.md @@ -0,0 +1,45 @@ +# 477 OnboardingWizard + +引导向导。 + +## 用途 + +多步引导:进度条、步骤内容、上一步、下一步、跳过与完成。 + +## 最小示例 + +```go +step := 0 +steps := []mujica.WizardStep{{Title: "Welcome"}, {Title: "Pick a theme"}, {Title: "Done"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.OnboardingWizard(c, &step, steps, mujica.OnboardingWizardOptions{}).Finished() { + fmt.Println("onboarded") + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `step` | `*int` | 必填 | 当前步骤,越界 panic。 | +| `steps` | `[]WizardStep` | 必填 | Title 必填;Description、Content、Ready。 | +| `NoSkip` | `bool` | false | 不显示跳过。 | + +## 状态 + +各步骤、未就绪(下一步禁用)、最后一步(完成)。 + +## 事件 + +`Finished()`、`Skipped()`。 + +## 键盘操作 + +下一步按钮自动聚焦;Tab 在按钮与内容间移动。 + +## 限制 + +- 无。 diff --git a/docs/components/478-coachmark.md b/docs/components/478-coachmark.md new file mode 100644 index 0000000..2241266 --- /dev/null +++ b/docs/components/478-coachmark.md @@ -0,0 +1,43 @@ +# 478 Coachmark + +功能提示。 + +## 用途 + +在锚点元素旁显示提示,锚点加框;多步导航;Escape 或点击外部关闭。 + +## 最小示例 + +```go +open, step := true, 0 +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + compose := mujica.Button(c, "Compose", mujica.ButtonOptions{}) + mujica.Coachmark(c, &open, &step, []mujica.CoachmarkStep{{Anchor: compose, Title: "Write here", Body: "Start a new letter."}}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `open` | `*bool` | 必填 | 是否显示。 | +| `step` | `*int` | 必填 | 当前步骤。 | +| `steps` | `[]CoachmarkStep` | 必填 | Anchor(本帧已构建的元素)、Title 必填,Body 可选。 | + +## 状态 + +显示、各步骤、最后一步(完成)、关闭。 + +## 事件 + +`Finished()`;`Dismissed()` 在 Escape、点击外部或跳过时为 true。 + +## 键盘操作 + +下一步按钮自动聚焦;Escape 关闭。 + +## 限制 + +- 弹层沿用 Popover 定位,空间不足时翻转到锚点上方。 diff --git a/docs/components/479-whats-new-dialog.md b/docs/components/479-whats-new-dialog.md new file mode 100644 index 0000000..e957c31 --- /dev/null +++ b/docs/components/479-whats-new-dialog.md @@ -0,0 +1,44 @@ +# 479 WhatsNewDialog + +更新内容。 + +## 用途 + +以带饰边的对话框列出各版本的更新(新增、改进、修复),提供“不再显示”。 + +## 最小示例 + +```go +open, hide := true, false +releases := []mujica.Release{{Version: "1.1", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeNew, Text: "500 components."}}}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.WhatsNewDialog(c, &open, &hide, releases, mujica.WhatsNewDialogOptions{}) +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `open` | `*bool` | 必填 | 是否显示。 | +| `hide` | `*bool` | 必填 | “不再显示”的值,由调用方保存。 | +| `releases` | `[]Release` | 必填 | Version 必填;Date、Changes。 | +| `Title` | `string` | 内置“What's new” | 标题。 | + +## 状态 + +显示、勾选不再显示。 + +## 事件 + +`ChoiceResult.Changed()` 在“不再显示”变化时。 + +## 键盘操作 + +“知道了”自动聚焦;Escape 关闭。 + +## 限制 + +- 无。 diff --git a/docs/components/480-feedback-widget.md b/docs/components/480-feedback-widget.md new file mode 100644 index 0000000..9b89cb1 --- /dev/null +++ b/docs/components/480-feedback-widget.md @@ -0,0 +1,46 @@ +# 480 FeedbackWidget + +反馈组件。 + +## 用途 + +选择反馈类型、填写内容、附件插槽,提交一次后等待调用方完成。基于 Form。 + +## 最小示例 + +```go +var state mujica.FeedbackWidgetState +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if f, ok := mujica.FeedbackWidget(c, &state, mujica.FeedbackWidgetOptions{}).Submitted(); ok { + fmt.Println("send", f.Kind) + state.EndSubmit("") + } +} +ui.Render(view, 400, 300, 1) +``` + +## 参数 + +| 字段 | 类型 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `s` | `*FeedbackWidgetState` | 必填 | Draft 与 Form。 | +| `Attachments` | `func()` | nil | 截图或文件插槽。 | +| `MinLength` | `int` | 10 | 最少字数。 | +| `Disabled` | `bool` | false | 禁用。 | + +## 状态 + +填写、过短错误、提交中、失败、禁用。提交中锁定类型、正文与附件控件,失败保留原稿。 + +## 事件 + +`Submitted()` 返回反馈;完成后调用 `s.EndSubmit(err)`,成功时清空内容。 + +## 键盘操作 + +类型分段用方向键切换;Tab 到内容与按钮。 + +## 限制 + +- 截图采集由调用方完成。 diff --git a/example_account_test.go b/example_account_test.go new file mode 100644 index 0000000..cc29618 --- /dev/null +++ b/example_account_test.go @@ -0,0 +1,240 @@ +package mujica_test + +import ( + "fmt" + "time" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +func ExampleSettingsLayout() { + page := "" + sound := true + pages := []mujica.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.SettingsLayout(c, &page, pages, mujica.SettingsLayoutOptions{}, func(id string) { + mujica.SettingsSection(c, "Notifications", func() { + mujica.SettingsRow(c, "Sound", mujica.SettingsRowOptions{ID: "sound"}, func() *ui.Element { + return mujica.Switch(c, &sound, "", mujica.SwitchOptions{}) + }) + }) + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleSettingsSearch() { + query, page := "", "general" + entries := []mujica.SettingEntry{{ID: "sound", Page: "general", Label: "Sound", Keywords: []string{"audio"}}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if e, ok := mujica.SettingsSearch(c, &query, entries, mujica.SettingsSearchOptions{}).Chosen(); ok { + page = e.Page + } + fmt.Println("page", page) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleThemeSelector() { + mode := mujica.System + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{Mode: mode}) + mujica.ThemeSelector(c, &mode, mujica.ThemeSelectorOptions{}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleAccentColorPicker() { + accent := ui.Hex("#7D2034") + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.AccentColorPicker(c, &accent, mujica.AccentColorPickerOptions{}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleShortcutRecorder() { + palette := mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ShortcutRecorder(c, &palette, mujica.ShortcutRecorderOptions{ + Label: "Command palette", + Taken: []mujica.ShortcutBinding{{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}}, + }) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleKeyboardShortcutsList() { + query := "" + groups := []mujica.ShortcutGroup{{Title: "General", Bindings: []mujica.ShortcutBinding{ + {Action: "Command palette", Shortcut: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}}, + }}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.KeyboardShortcutsList(c, &query, groups, mujica.KeyboardShortcutsListOptions{}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleLoginForm() { + var state mujica.LoginFormState + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.LoginForm(c, &state, mujica.LoginFormOptions{OnSubmit: func(account, password string, remember bool) { + // Submit credentials through the caller-provided auth service. + state.EndSubmit("") + }}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleOAuthButtons() { + providers := []mujica.OAuthProvider{{ID: "sso", Name: "Company SSO"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if id, ok := mujica.OAuthButtons(c, providers, mujica.OAuthButtonsOptions{}).Chosen(); ok { + fmt.Println("start sign-in with", id) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleTwoFactorInput() { + code := "" + resendAt := time.Now().Add(30 * time.Second) + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + r := mujica.TwoFactorInput(c, &code, mujica.TwoFactorInputOptions{ResendAt: resendAt}) + if r.Completed() { + fmt.Println("verify the code") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleProfileEditor() { + saved := mujica.Profile{Name: "Sakiko", Bio: "Keyboard."} + profile := saved + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.ProfileEditor(c, &profile, mujica.ProfileEditorOptions{Saved: saved}).Save() { + saved = profile + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleAccountSwitcher() { + current := "a" + accounts := []mujica.Account{{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.AccountSwitcher(c, ¤t, accounts, mujica.AccountSwitcherOptions{}).Add() { + fmt.Println("sign in another account") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleWorkspaceSwitcher() { + current := "ave" + spaces := []mujica.Workspace{{ID: "ave", Name: "Ave Mujica"}, {ID: "mygo", Name: "MyGO!!!!!"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.WorkspaceSwitcher(c, ¤t, spaces, mujica.WorkspaceSwitcherOptions{}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleUserMenu() { + status := mujica.PresenceOnline + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.UserMenu(c, &status, mujica.UserMenuOptions{Name: "Sakiko Togawa"}).SignOut() { + fmt.Println("sign out") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleSessionList() { + sessions := []mujica.Session{{ID: "s1", Device: "MacBook Pro", Current: true}, {ID: "s2", Device: "iPhone", Location: "Osaka"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if id, ok := mujica.SessionList(c, sessions, mujica.SessionListOptions{}).Revoked(); ok { + fmt.Println("sign out", id) + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleApiKeyManager() { + var state mujica.ApiKeyManagerState + keys := []mujica.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if name, ok := mujica.ApiKeyManager(c, &state, keys, mujica.ApiKeyManagerOptions{}).Create(); ok { + fmt.Println("create", name) + state.Reveal("secret from your key service") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleUsageQuota() { + items := []mujica.QuotaItem{{Label: "API calls", Used: 4200, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.UsageQuota(c, items, mujica.UsageQuotaOptions{ResetAt: time.Now().Add(72 * time.Hour)}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleOnboardingWizard() { + step := 0 + steps := []mujica.WizardStep{{Title: "Welcome"}, {Title: "Pick a theme"}, {Title: "Done"}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.OnboardingWizard(c, &step, steps, mujica.OnboardingWizardOptions{}).Finished() { + fmt.Println("onboarded") + } + } + ui.Render(view, 400, 300, 1) +} + +func ExampleCoachmark() { + open, step := true, 0 + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + compose := mujica.Button(c, "Compose", mujica.ButtonOptions{}) + mujica.Coachmark(c, &open, &step, []mujica.CoachmarkStep{{Anchor: compose, Title: "Write here", Body: "Start a new letter."}}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleWhatsNewDialog() { + open, hide := true, false + releases := []mujica.Release{{Version: "1.1", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeNew, Text: "500 components."}}}} + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.WhatsNewDialog(c, &open, &hide, releases, mujica.WhatsNewDialogOptions{}) + } + ui.Render(view, 400, 300, 1) +} + +func ExampleFeedbackWidget() { + var state mujica.FeedbackWidgetState + view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if f, ok := mujica.FeedbackWidget(c, &state, mujica.FeedbackWidgetOptions{}).Submitted(); ok { + fmt.Println("send", f.Kind) + state.EndSubmit("") + } + } + ui.Render(view, 400, 300, 1) +} diff --git a/examples/gallery/pages_account.go b/examples/gallery/pages_account.go index 022f3f7..7a27e3c 100644 --- a/examples/gallery/pages_account.go +++ b/examples/gallery/pages_account.go @@ -1,3 +1,155 @@ package main -var accountPages = []page{} +import ( + "fmt" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +const accountCategory = "Settings & Account" + +var accountPages = []page{ + {ID: "461", Name: "SettingsLayout", ZhName: "设置布局", Category: accountCategory, Code: accountCode["SettingsLayout"], Demo: accountSettingsDemo, + Keys: "Arrows move in the navigation; Tab enters the page; clicking a row's text focuses its control."}, + {ID: "462", Name: "SettingsSearch", ZhName: "设置搜索", Category: accountCategory, Code: accountCode["SettingsSearch"], Demo: accountSearchDemo, + Keys: "Type to search; Up and Down move; Enter jumps to the setting and focuses it."}, + {ID: "463", Name: "ThemeSelector", ZhName: "主题选择", Category: accountCategory, Code: accountCode["ThemeSelector"], Demo: accountThemeDemo, + Keys: "Left and Right move between cards; Space picks."}, + {ID: "464", Name: "AccentColorPicker", ZhName: "主色选择", Category: accountCategory, Code: accountCode["AccentColorPicker"], Demo: accountAccentDemo, + Keys: "Left and Right move between swatches; Space picks."}, + {ID: "465", Name: "ShortcutRecorder", ZhName: "快捷键录制", Category: accountCategory, Code: accountCode["ShortcutRecorder"], Demo: accountRecorderDemo, + Keys: "Enter or Space records; press the combination; Escape cancels."}, + {ID: "466", Name: "KeyboardShortcutsList", ZhName: "快捷键列表", Category: accountCategory, Code: accountCode["KeyboardShortcutsList"], Demo: accountShortcutsDemo, + Keys: "Type in the search field to filter by action or key."}, + {ID: "467", Name: "LoginForm", ZhName: "登录表单", Category: accountCategory, Code: accountCode["LoginForm"], Demo: accountLoginDemo, + Keys: "Enter in any field signs in; an empty field takes the focus."}, + {ID: "468", Name: "OAuthButtons", ZhName: "第三方登录按钮", Category: accountCategory, Code: accountCode["OAuthButtons"], Demo: accountOAuthDemo, + Keys: "Tab between providers; Enter or Space continues."}, + {ID: "469", Name: "TwoFactorInput", ZhName: "两步验证码", Category: accountCategory, Code: accountCode["TwoFactorInput"], Demo: accountTwoFactorDemo, + Keys: "Type or paste digits; Backspace clears; Tab reaches Resend."}, + {ID: "470", Name: "ProfileEditor", ZhName: "个人资料编辑", Category: accountCategory, Code: accountCode["ProfileEditor"], Demo: accountProfileDemo, + Keys: "Tab through picture, name, bio, Discard and Save."}, + {ID: "471", Name: "AccountSwitcher", ZhName: "账户切换", Category: accountCategory, Code: accountCode["AccountSwitcher"], Demo: accountSwitcherDemo, + Keys: "Enter, Space or Down opens; arrows move; Enter chooses; Escape closes."}, + {ID: "472", Name: "WorkspaceSwitcher", ZhName: "工作区切换", Category: accountCategory, Code: accountCode["WorkspaceSwitcher"], Demo: accountWorkspaceDemo, + Keys: "Open, type to search, Up and Down move, Enter switches, Escape closes."}, + {ID: "473", Name: "UserMenu", ZhName: "用户菜单", Category: accountCategory, Code: accountCode["UserMenu"], Demo: accountUserMenuDemo, + Keys: "Enter, Space or Down opens; arrows move; Enter chooses."}, + {ID: "474", Name: "SessionList", ZhName: "登录会话", Category: accountCategory, Code: accountCode["SessionList"], Demo: accountSessionsDemo, + Keys: "Tab to each Sign out; the confirmation focuses Cancel."}, + {ID: "475", Name: "ApiKeyManager", ZhName: "API 密钥管理", Category: accountCategory, Code: accountCode["ApiKeyManager"], Demo: accountKeysDemo, + Keys: "Enter in the name field creates; Copy key copies; Revoke asks first."}, + {ID: "476", Name: "UsageQuota", ZhName: "用量配额", Category: accountCategory, Code: accountCode["UsageQuota"], Demo: accountQuotaDemo, + Keys: "Display only; each bar reads as a meter."}, + {ID: "477", Name: "OnboardingWizard", ZhName: "引导向导", Category: accountCategory, Code: accountCode["OnboardingWizard"], Demo: accountWizardDemo, + Keys: "Next has the focus; Tab reaches Back and Skip."}, + {ID: "478", Name: "Coachmark", ZhName: "功能提示", Category: accountCategory, Code: accountCode["Coachmark"], Demo: accountCoachDemo, + Keys: "Next has the focus; Escape closes the tour."}, + {ID: "479", Name: "WhatsNewDialog", ZhName: "更新内容", Category: accountCategory, Code: accountCode["WhatsNewDialog"], Demo: accountWhatsNewDemo, + Keys: "Got it has the focus; Escape closes."}, + {ID: "480", Name: "FeedbackWidget", ZhName: "反馈组件", Category: accountCategory, Code: accountCode["FeedbackWidget"], Demo: accountFeedbackDemo, + Keys: "Arrows switch the type; Tab reaches the message and buttons."}, +} + +type accountSettingsState struct { + page, query string + sound, digest, report bool +} + +var accountSettingPages = []mujica.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}} + +var accountSettingEntries = []mujica.SettingEntry{ + {ID: "sound", Page: "general", Label: "Sound", Description: "Chime on new messages.", Keywords: []string{"audio", "notification"}}, + {ID: "digest", Page: "general", Label: "Weekly digest", Description: "A summary every Monday.", Keywords: []string{"email"}}, + {ID: "report", Page: "privacy", Label: "Usage reports", Description: "Send anonymous usage."}, +} + +// accountSettingsPage builds the demo pages shared by 461 and 462. +func accountSettingsPage(c *ui.Context, d *accountSettingsState, log func(string)) { + ui.Box(c).Width(900).Height(320).Children(func() { + if mujica.SettingsLayout(c, &d.page, accountSettingPages, mujica.SettingsLayoutOptions{}, func(id string) { + switch id { + case "general": + mujica.SettingsSection(c, "Notifications", func() { + accountSwitchRow(c, "sound", "Sound", "Chime on new messages.", &d.sound, log) + accountSwitchRow(c, "digest", "Weekly digest", "A summary every Monday.", &d.digest, log) + }) + case "privacy": + mujica.SettingsSection(c, "Data", func() { + accountSwitchRow(c, "report", "Usage reports", "Send anonymous usage.", &d.report, log) + }) + } + }).Changed() { + log("page " + d.page) + } + }) +} + +// accountSwitchRow is a settings row with a switch. +func accountSwitchRow(c *ui.Context, id, label, desc string, v *bool, log func(string)) { + mujica.SettingsRow(c, label, mujica.SettingsRowOptions{ID: id, Description: desc}, func() *ui.Element { + sw := mujica.Switch(c, v, "", mujica.SwitchOptions{}) + if sw.Changed() { + log(fmt.Sprintf("%s %v", label, *v)) + } + return sw + }) +} + +func accountSettingsDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "461", func() accountSettingsState { return accountSettingsState{sound: true} }) + accountSettingsPage(c, d, log) +} + +func accountSearchDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "462", func() accountSettingsState { return accountSettingsState{sound: true} }) + ui.Box(c).Width(320).Children(func() { + if e, ok := mujica.SettingsSearch(c, &d.query, accountSettingEntries, mujica.SettingsSearchOptions{}).Chosen(); ok { + d.page = e.Page + log("jump to " + e.ID) + } + }) + accountSettingsPage(c, d, log) +} + +type accountThemeState struct { + mode mujica.Mode + disabled bool +} + +func accountThemeDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "463", func() accountThemeState { return accountThemeState{} }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + if mujica.ThemeSelector(c, &d.mode, mujica.ThemeSelectorOptions{Disabled: d.disabled}).Changed() { + log("theme " + d.mode.String()) + } +} + +type accountAccentState struct { + color ui.Color + disabled bool +} + +func accountAccentDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "464", func() accountAccentState { return accountAccentState{color: ui.Hex("#7D2034")} }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + if mujica.AccentColorPicker(c, &d.color, mujica.AccentColorPickerOptions{Disabled: d.disabled}).Changed() { + log(fmt.Sprintf("accent %v", d.color)) + } + mujica.Button(c, "Preview button", mujica.ButtonOptions{}).Background(d.color) +} + +func accountToggles(c *ui.Context, log func(string), labels []string, values ...*bool) { + before := make([]bool, len(values)) + for i, v := range values { + before[i] = *v + } + formToggles(c, labels, values...) + for i, v := range values { + if before[i] != *v { + log(fmt.Sprintf("%s %v", labels[i], *v)) + } + } +} diff --git a/examples/gallery/pages_account_auth.go b/examples/gallery/pages_account_auth.go new file mode 100644 index 0000000..47dc01b --- /dev/null +++ b/examples/gallery/pages_account_auth.go @@ -0,0 +1,159 @@ +package main + +import ( + "fmt" + "time" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +type accountRecorderState struct { + palette, zen mujica.Shortcut + disabled bool +} + +var accountTaken = []mujica.ShortcutBinding{{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}} + +func accountRecorderDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "465", func() accountRecorderState { + return accountRecorderState{palette: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}} + }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + ui.Text(c, "Cmd/Ctrl+S is taken by Save.") + for _, r := range []struct { + name string + v *mujica.Shortcut + }{{"Command palette", &d.palette}, {"Zen mode", &d.zen}} { + if mujica.ShortcutRecorder(c, r.v, mujica.ShortcutRecorderOptions{Label: r.name, Taken: accountTaken, Disabled: d.disabled}).Changed() { + log(fmt.Sprintf("%s = %s", r.name, r.v.String())) + } + } +} + +type accountShortcutsState struct{ query string } + +func accountShortcutsDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "466", func() accountShortcutsState { return accountShortcutsState{} }) + groups := []mujica.ShortcutGroup{ + {Title: "General", Bindings: []mujica.ShortcutBinding{ + {Action: "Command palette", Shortcut: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}}, + {Action: "Settings", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyComma}}, + {Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}, + }}, + {Title: "Editing", Bindings: []mujica.ShortcutBinding{ + {Action: "Duplicate line", Shortcut: mujica.Shortcut{Mods: ui.Alt | ui.Shift, Key: ui.KeyDown}}, + {Action: "Toggle comment", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeySlash}}, + {Action: "Zen mode"}, + }}, + } + before := d.query + ui.Box(c).Width(480).Children(func() { mujica.KeyboardShortcutsList(c, &d.query, groups, mujica.KeyboardShortcutsListOptions{}) }) + if d.query != before { + log(fmt.Sprintf("search %q", d.query)) + } +} + +type accountLoginState struct { + form mujica.LoginFormState + disabled, reject bool + attempts int +} + +func accountLoginDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "467", func() accountLoginState { return accountLoginState{reject: true} }) + done := *ui.Local(c.Root(), "467.done", func() chan string { return make(chan string, 1) }) + select { + case e := <-done: + d.form.EndSubmit(e) + log(fmt.Sprintf("sign-in finished, error %q", e)) + default: + } + accountToggles(c, log, []string{"Disabled", "Server rejects"}, &d.disabled, &d.reject) + mujica.LoginForm(c, &d.form, mujica.LoginFormOptions{Disabled: d.disabled, OnSubmit: func(account, password string, remember bool) { + // Credentials stay in the caller's sign-in request. + d.attempts++ + log(fmt.Sprintf("submit %q, remember %v", account, remember)) + fail := "" + switch { + case d.attempts >= 3: + fail = "Too many attempts. Try again in 5 minutes." + case d.reject: + fail = "Wrong account or password." + } + go func() { + time.Sleep(700 * time.Millisecond) + done <- fail + c.Invalidate() + }() + }}) +} + +type accountOAuthState struct { + pending string + disabled bool +} + +func accountOAuthDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "468", func() accountOAuthState { return accountOAuthState{} }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + providers := []mujica.OAuthProvider{{ID: "git", Name: "Git host"}, {ID: "sso", Name: "Company SSO"}, {ID: "mail", Name: "Email link"}} + if id, ok := mujica.OAuthButtons(c, providers, mujica.OAuthButtonsOptions{Pending: d.pending, Disabled: d.disabled}).Chosen(); ok { + d.pending = id + log("continue with " + id) + } + if d.pending != "" && tradingAction(c, "Finish sign-in") { + log("sign-in with " + d.pending + " finished") + d.pending = "" + } +} + +type accountTwoFactorState struct { + code string + resendAt time.Time + disabled bool + err string +} + +func accountTwoFactorDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "469", func() accountTwoFactorState { return accountTwoFactorState{} }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + r := mujica.TwoFactorInput(c, &d.code, mujica.TwoFactorInputOptions{ResendAt: d.resendAt, Error: d.err, Disabled: d.disabled}) + if r.Completed() { + // The code is checked here and never logged. + d.err = "" + if d.code != "123456" { + d.err = "That code is not right." + } + log(fmt.Sprintf("code complete, accepted %v", d.err == "")) + } + if r.Resend() { + d.resendAt, d.code, d.err = c.Now().Add(30*time.Second), "", "" + log("resend requested") + } +} + +type accountProfileState struct { + saved, edit mujica.Profile + saving, disabled bool +} + +func accountProfileDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "470", func() accountProfileState { + p := mujica.Profile{Name: "Sakiko Togawa", Bio: "Keyboard, Ave Mujica."} + return accountProfileState{saved: p, edit: p} + }) + accountToggles(c, log, []string{"Saving", "Disabled"}, &d.saving, &d.disabled) + r := mujica.ProfileEditor(c, &d.edit, mujica.ProfileEditorOptions{Saved: d.saved, Saving: d.saving, Disabled: d.disabled}) + if r.Save() { + d.saved = d.edit + log("saved") + } + if r.Discarded() { + log("discarded") + } + if r.ChangeAvatar() { + log("change picture") + } +} diff --git a/examples/gallery/pages_account_code.go b/examples/gallery/pages_account_code.go new file mode 100644 index 0000000..70d4a92 --- /dev/null +++ b/examples/gallery/pages_account_code.go @@ -0,0 +1,175 @@ +package main + +// accountCode contains the compiled Example bodies. +var accountCode = map[string]string{ + "SettingsLayout": `page := "" +sound := true +pages := []mujica.SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.SettingsLayout(c, &page, pages, mujica.SettingsLayoutOptions{}, func(id string) { + mujica.SettingsSection(c, "Notifications", func() { + mujica.SettingsRow(c, "Sound", mujica.SettingsRowOptions{ID: "sound"}, func() *ui.Element { + return mujica.Switch(c, &sound, "", mujica.SwitchOptions{}) + }) + }) + }) +} +ui.Render(view, 400, 300, 1)`, + "SettingsSearch": `query, page := "", "general" +entries := []mujica.SettingEntry{{ID: "sound", Page: "general", Label: "Sound", Keywords: []string{"audio"}}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if e, ok := mujica.SettingsSearch(c, &query, entries, mujica.SettingsSearchOptions{}).Chosen(); ok { + page = e.Page + } + fmt.Println("page", page) +} +ui.Render(view, 400, 300, 1)`, + "ThemeSelector": `mode := mujica.System +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{Mode: mode}) + mujica.ThemeSelector(c, &mode, mujica.ThemeSelectorOptions{}) +} +ui.Render(view, 400, 300, 1)`, + "AccentColorPicker": `accent := ui.Hex("#7D2034") +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.AccentColorPicker(c, &accent, mujica.AccentColorPickerOptions{}) +} +ui.Render(view, 400, 300, 1)`, + "ShortcutRecorder": `palette := mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.ShortcutRecorder(c, &palette, mujica.ShortcutRecorderOptions{ + Label: "Command palette", + Taken: []mujica.ShortcutBinding{{Action: "Save", Shortcut: mujica.Shortcut{Mods: ui.Cmd, Key: ui.KeyS}}}, + }) +} +ui.Render(view, 400, 300, 1)`, + "KeyboardShortcutsList": `query := "" +groups := []mujica.ShortcutGroup{{Title: "General", Bindings: []mujica.ShortcutBinding{ + {Action: "Command palette", Shortcut: mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP}}, +}}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.KeyboardShortcutsList(c, &query, groups, mujica.KeyboardShortcutsListOptions{}) +} +ui.Render(view, 400, 300, 1)`, + "LoginForm": `var state mujica.LoginFormState +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.LoginForm(c, &state, mujica.LoginFormOptions{OnSubmit: func(account, password string, remember bool) { + // Submit credentials through the caller-provided auth service. + state.EndSubmit("") + }}) +} +ui.Render(view, 400, 300, 1)`, + "OAuthButtons": `providers := []mujica.OAuthProvider{{ID: "sso", Name: "Company SSO"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if id, ok := mujica.OAuthButtons(c, providers, mujica.OAuthButtonsOptions{}).Chosen(); ok { + fmt.Println("start sign-in with", id) + } +} +ui.Render(view, 400, 300, 1)`, + "TwoFactorInput": `code := "" +resendAt := time.Now().Add(30 * time.Second) +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + r := mujica.TwoFactorInput(c, &code, mujica.TwoFactorInputOptions{ResendAt: resendAt}) + if r.Completed() { + fmt.Println("verify the code") + } +} +ui.Render(view, 400, 300, 1)`, + "ProfileEditor": `saved := mujica.Profile{Name: "Sakiko", Bio: "Keyboard."} +profile := saved +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.ProfileEditor(c, &profile, mujica.ProfileEditorOptions{Saved: saved}).Save() { + saved = profile + } +} +ui.Render(view, 400, 300, 1)`, + "AccountSwitcher": `current := "a" +accounts := []mujica.Account{{ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.AccountSwitcher(c, ¤t, accounts, mujica.AccountSwitcherOptions{}).Add() { + fmt.Println("sign in another account") + } +} +ui.Render(view, 400, 300, 1)`, + "WorkspaceSwitcher": `current := "ave" +spaces := []mujica.Workspace{{ID: "ave", Name: "Ave Mujica"}, {ID: "mygo", Name: "MyGO!!!!!"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.WorkspaceSwitcher(c, ¤t, spaces, mujica.WorkspaceSwitcherOptions{}) +} +ui.Render(view, 400, 300, 1)`, + "UserMenu": `status := mujica.PresenceOnline +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.UserMenu(c, &status, mujica.UserMenuOptions{Name: "Sakiko Togawa"}).SignOut() { + fmt.Println("sign out") + } +} +ui.Render(view, 400, 300, 1)`, + "SessionList": `sessions := []mujica.Session{{ID: "s1", Device: "MacBook Pro", Current: true}, {ID: "s2", Device: "iPhone", Location: "Osaka"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if id, ok := mujica.SessionList(c, sessions, mujica.SessionListOptions{}).Revoked(); ok { + fmt.Println("sign out", id) + } +} +ui.Render(view, 400, 300, 1)`, + "ApiKeyManager": `var state mujica.ApiKeyManagerState +keys := []mujica.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if name, ok := mujica.ApiKeyManager(c, &state, keys, mujica.ApiKeyManagerOptions{}).Create(); ok { + fmt.Println("create", name) + state.Reveal("secret from your key service") + } +} +ui.Render(view, 400, 300, 1)`, + "UsageQuota": `items := []mujica.QuotaItem{{Label: "API calls", Used: 4200, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.UsageQuota(c, items, mujica.UsageQuotaOptions{ResetAt: time.Now().Add(72 * time.Hour)}) +} +ui.Render(view, 400, 300, 1)`, + "OnboardingWizard": `step := 0 +steps := []mujica.WizardStep{{Title: "Welcome"}, {Title: "Pick a theme"}, {Title: "Done"}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if mujica.OnboardingWizard(c, &step, steps, mujica.OnboardingWizardOptions{}).Finished() { + fmt.Println("onboarded") + } +} +ui.Render(view, 400, 300, 1)`, + "Coachmark": `open, step := true, 0 +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + compose := mujica.Button(c, "Compose", mujica.ButtonOptions{}) + mujica.Coachmark(c, &open, &step, []mujica.CoachmarkStep{{Anchor: compose, Title: "Write here", Body: "Start a new letter."}}) +} +ui.Render(view, 400, 300, 1)`, + "WhatsNewDialog": `open, hide := true, false +releases := []mujica.Release{{Version: "1.1", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeNew, Text: "500 components."}}}} +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + mujica.WhatsNewDialog(c, &open, &hide, releases, mujica.WhatsNewDialogOptions{}) +} +ui.Render(view, 400, 300, 1)`, + "FeedbackWidget": `var state mujica.FeedbackWidgetState +view := func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + if f, ok := mujica.FeedbackWidget(c, &state, mujica.FeedbackWidgetOptions{}).Submitted(); ok { + fmt.Println("send", f.Kind) + state.EndSubmit("") + } +} +ui.Render(view, 400, 300, 1)`, +} diff --git a/examples/gallery/pages_account_more.go b/examples/gallery/pages_account_more.go new file mode 100644 index 0000000..210f07d --- /dev/null +++ b/examples/gallery/pages_account_more.go @@ -0,0 +1,276 @@ +package main + +import ( + "fmt" + "time" + + "github.com/egoist/mygo/ui" + + mujica "mujicaui" +) + +type accountSwitchState struct { + account, space string + accounts []mujica.Account + disabled bool +} + +func accountSwitcherDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "471", func() accountSwitchState { + return accountSwitchState{account: "a", accounts: []mujica.Account{ + {ID: "a", Name: "Sakiko Togawa", Email: "sakiko@court"}, {ID: "b", Name: "Oblivionis", Email: "ob@mask"}}} + }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + r := mujica.AccountSwitcher(c, &d.account, d.accounts, mujica.AccountSwitcherOptions{Disabled: d.disabled}) + if r.Changed() { + log("switched to " + d.account) + } + if r.Add() { + n := len(d.accounts) + 1 + d.accounts = append(d.accounts, mujica.Account{ID: fmt.Sprint(n), Name: fmt.Sprintf("Guest %d", n)}) + log("add account") + } +} + +var accountSpaces = []mujica.Workspace{ + {ID: "ave", Name: "Ave Mujica", Detail: "5 members"}, {ID: "mygo", Name: "MyGO!!!!!", Detail: "5 members"}, + {ID: "crychic", Name: "CRYCHIC", Detail: "Archived"}, {ID: "sumimi", Name: "sumimi", Detail: "2 members"}, +} + +func accountWorkspaceDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "472", func() accountSwitchState { return accountSwitchState{space: "ave"} }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + if mujica.WorkspaceSwitcher(c, &d.space, accountSpaces, mujica.WorkspaceSwitcherOptions{Disabled: d.disabled}).Changed() { + log("workspace " + d.space) + } +} + +type accountUserState struct { + status mujica.UserStatus + disabled bool +} + +func accountUserMenuDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "473", func() accountUserState { return accountUserState{} }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + r := mujica.UserMenu(c, &d.status, mujica.UserMenuOptions{Name: "Sakiko Togawa", Email: "sakiko@court", Disabled: d.disabled}) + if r.StatusChanged() { + log(fmt.Sprintf("status %d", d.status)) + } + if r.Settings() { + log("open settings") + } + if r.SignOut() { + log("sign out") + } +} + +type accountSessionsState struct { + sessions []mujica.Session + disabled bool +} + +func accountSessionsDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "474", func() accountSessionsState { + return accountSessionsState{sessions: []mujica.Session{ + {ID: "s1", Device: "MacBook Pro · Safari", Location: "Tokyo", LastActive: time.Now(), Current: true}, + {ID: "s2", Device: "iPhone · App", Location: "Osaka", LastActive: time.Now().Add(-26 * time.Hour)}, + {ID: "s3", Device: "Windows · Edge", Location: "Kyoto"}, + }} + }) + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + r := mujica.SessionList(c, d.sessions, mujica.SessionListOptions{Disabled: d.disabled}) + if id, ok := r.Revoked(); ok { + for i, s := range d.sessions { + if s.ID == id { + d.sessions = append(d.sessions[:i:i], d.sessions[i+1:]...) + break + } + } + log("signed out " + id) + } + if r.OthersRevoked() { + d.sessions = d.sessions[:1] + log("signed out every other session") + } +} + +type accountKeysState struct { + state mujica.ApiKeyManagerState + keys []mujica.APIKey + disabled, reject bool +} + +func accountKeysDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "475", func() accountKeysState { + return accountKeysState{keys: []mujica.APIKey{{ID: "k1", Name: "Deploy bot", Prefix: "mk_live_9f2c", Created: time.Now().AddDate(0, -1, 0)}}} + }) + accountToggles(c, log, []string{"Disabled", "Create fails"}, &d.disabled, &d.reject) + r := mujica.ApiKeyManager(c, &d.state, d.keys, mujica.ApiKeyManagerOptions{Disabled: d.disabled}) + if name, ok := r.Create(); ok { + if d.reject { + d.state.Fail("Key limit reached.") + log("create failed") + return + } + // The caller supplies the generated key. + n := len(d.keys) + 1 + prefix := fmt.Sprintf("mk_live_%04x", n*7919) + d.keys = append(d.keys, mujica.APIKey{ID: fmt.Sprint("k", n), Name: name, Prefix: prefix, Created: time.Now()}) + d.state.Reveal(fmt.Sprintf("%s_%x", prefix, time.Now().UnixNano())) + log("created key " + name) + } + if id, ok := r.Revoked(); ok { + for i, k := range d.keys { + if k.ID == id { + d.keys = append(d.keys[:i:i], d.keys[i+1:]...) + break + } + } + log("revoked " + id) + } +} + +type accountQuotaState struct{ calls float64 } + +func accountQuotaDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "476", func() accountQuotaState { return accountQuotaState{calls: 4200} }) + if mujica.Slider(c, &d.calls, mujica.SliderOptions{Min: 0, Max: 12000, Step: 100, Label: "API calls used", ShowValue: true}).Changed() { + log(fmt.Sprintf("calls %.0f", d.calls)) + } + ui.Box(c).Width(440).Children(func() { + mujica.UsageQuota(c, []mujica.QuotaItem{{Label: "API calls", Used: d.calls, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}}, + mujica.UsageQuotaOptions{ResetAt: time.Now().Add(52 * time.Hour)}) + }) +} + +type accountWizardState struct { + step int + theme mujica.Mode + name string + noSkip bool +} + +func accountWizardDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "477", func() accountWizardState { return accountWizardState{} }) + accountToggles(c, log, []string{"Hide Skip"}, &d.noSkip) + steps := []mujica.WizardStep{ + {Title: "Welcome", Description: "Three short steps to set up your court."}, + {Title: "Your name", Ready: func() bool { return d.name != "" }, Content: func() { + if mujica.TextInput(c, &d.name, mujica.TextInputOptions{Label: "Name", Placeholder: "Name"}).Changed() { + log("name changed") + } + }}, + {Title: "Pick a theme", Content: func() { + if mujica.ThemeSelector(c, &d.theme, mujica.ThemeSelectorOptions{}).Changed() { + log("theme " + d.theme.String()) + } + }}, + } + from := d.step + r := mujica.OnboardingWizard(c, &d.step, steps, mujica.OnboardingWizardOptions{NoSkip: d.noSkip}) + if d.step != from { + log(fmt.Sprintf("step %d", d.step+1)) + } + if r.Finished() || r.Skipped() { + log(fmt.Sprintf("finished %v skipped %v", r.Finished(), r.Skipped())) + d.step = 0 + } +} + +type accountCoachState struct { + open, disabled bool + step int +} + +func accountCoachDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "478", func() accountCoachState { return accountCoachState{} }) + accountToggles(c, log, []string{"Disabled anchor"}, &d.disabled) + if tradingAction(c, "Start tour") { + d.open, d.step = true, 0 + log("tour started") + } + var compose, archive *ui.Element + ui.Row(c).Gap(48).Padding(8).Children(func() { + compose = mujica.Button(c, "Compose", mujica.ButtonOptions{Disabled: d.disabled}) + archive = mujica.Button(c, "Archive", mujica.ButtonOptions{Variant: mujica.Secondary}) + }) + if compose.Clicked() { + log("compose") + } + if archive.Clicked() { + log("archive") + } + from := d.step + r := mujica.Coachmark(c, &d.open, &d.step, []mujica.CoachmarkStep{ + {Anchor: compose, Title: "Write here", Body: "Start a new letter."}, + {Anchor: archive, Title: "Keep it tidy", Body: "Archive threads you have finished."}, + }) + if d.step != from { + log(fmt.Sprintf("tip %d", d.step+1)) + } + if r.Finished() { + log("tour finished") + } + if r.Dismissed() { + log("tour dismissed") + } +} + +type accountWhatsNewState struct{ open, hide bool } + +func accountWhatsNewDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "479", func() accountWhatsNewState { return accountWhatsNewState{} }) + if tradingAction(c, "Show what's new") { + d.open = true + log("open") + } + releases := []mujica.Release{ + {Version: "1.1", Date: "2026-10-05", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeNew, Text: "400 new components."}, {Kind: mujica.ChangeFixed, Text: "Snapshots no longer drift."}}}, + {Version: "1.0", Date: "2026-09-01", Changes: []mujica.ReleaseChange{{Kind: mujica.ChangeImproved, Text: "Court-gothic theme."}}}, + } + was := d.open + if mujica.WhatsNewDialog(c, &d.open, &d.hide, releases, mujica.WhatsNewDialogOptions{}).Changed() { + log(fmt.Sprintf("don't show again %v", d.hide)) + } + if was && !d.open { + log("closed") + } +} + +type accountFeedbackState struct { + state mujica.FeedbackWidgetState + disabled bool +} + +func accountFeedbackDemo(c *ui.Context, log func(string)) { + d := ui.Local(c.Root(), "480", func() accountFeedbackState { return accountFeedbackState{} }) + done := *ui.Local(c.Root(), "480.done", func() chan string { return make(chan string, 1) }) + select { + case e := <-done: + d.state.EndSubmit(e) + log("feedback sent") + default: + } + accountToggles(c, log, []string{"Disabled"}, &d.disabled) + before := d.state.Draft + r := mujica.FeedbackWidget(c, &d.state, mujica.FeedbackWidgetOptions{Disabled: d.disabled, Attachments: func() { + if tradingAction(c, "Attach screenshot") { + log("attach screenshot") + } + }}) + if before.Kind != d.state.Draft.Kind { + log("type changed") + } + if before.Text != d.state.Draft.Text { + log("message changed") + } + if f, ok := r.Submitted(); ok { + log(fmt.Sprintf("send kind %d, %d characters", f.Kind, len([]rune(f.Text)))) + go func() { + time.Sleep(600 * time.Millisecond) + done <- "" + c.Invalidate() + }() + } +} diff --git a/examples/gallery/zz_account_test.go b/examples/gallery/zz_account_test.go new file mode 100644 index 0000000..e986718 --- /dev/null +++ b/examples/gallery/zz_account_test.go @@ -0,0 +1,102 @@ +package main + +import ( + "github.com/egoist/mygo/ui" + mujica "mujicaui" + "strings" + "testing" +) + +func accountClick(t *testing.T, tt *ui.Tester, label string) { + t.Helper() + if err := tt.Click(label); err != nil { + t.Fatal(err) + } +} + +func TestAccountPagesInteractiveEvents(t *testing.T) { + actions := map[string]func(*testing.T, *ui.Tester){ + "461": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Privacy") }, + "462": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Search settings") + tt.Type("audio") + tt.Key(0, ui.KeyEnter) + }, + "463": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Dark") }, + "464": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "#355E80") }, + "465": func(t *testing.T, tt *ui.Tester) { + value := mujica.Shortcut{Mods: ui.Cmd | ui.Shift, Key: ui.KeyP} + accountClick(t, tt, "Command palette: "+value.String()) + tt.Key(ui.Cmd, ui.KeyK) + }, + "466": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Search shortcuts"); tt.Type("Save") }, + "467": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Email or username") + tt.Type("test@court") + accountClick(t, tt, "Password") + tt.Type("test-secret-467") + accountClick(t, tt, "Sign in") + }, + "468": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Continue with Git host") }, + "469": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Verification code"); tt.Type("381942") }, + "470": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Name") + tt.Type(" edited") + accountClick(t, tt, "Save") + }, + "471": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Sakiko Togawa") + accountClick(t, tt, "Oblivionis · ob@mask") + }, + "472": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Ave Mujica") + tt.Type("arch") + tt.Key(0, ui.KeyEnter) + }, + "473": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Sakiko Togawa, Online") + accountClick(t, tt, "Away") + }, + "474": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Sign out of iPhone · App") }, + "475": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Create key") + tt.Type("CI") + tt.Key(0, ui.KeyEnter) + }, + "476": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "API calls used"); tt.Key(0, ui.KeyRight) }, + "477": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Next") }, + "478": func(t *testing.T, tt *ui.Tester) { accountClick(t, tt, "Start tour"); accountClick(t, tt, "Next") }, + "479": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Show what's new") + accountClick(t, tt, "Don't show again") + }, + "480": func(t *testing.T, tt *ui.Tester) { + accountClick(t, tt, "Message") + tt.Type("This is a feedback test.") + accountClick(t, tt, "Send feedback") + }, + } + if len(accountPages) != 20 { + t.Fatalf("%d account pages", len(accountPages)) + } + for _, p := range accountPages { + t.Run(p.ID, func(t *testing.T) { + if p.Category != "Settings & Account" || p.Code == "" || p.Keys == "" { + t.Fatal("incomplete gallery metadata") + } + var events []string + tt := ui.NewTester(func(c *ui.Context) { + mujica.Use(c, mujica.Settings{}) + ui.Column(c).Padding(20).Gap(16).Children(func() { p.Demo(c, func(s string) { events = append(events, s) }) }) + }, 1100, 800) + actions[p.ID](t, tt) + if len(events) == 0 { + t.Fatal("interaction has no gallery event") + } + logs := strings.Join(events, "\n") + if strings.Contains(logs, "test-secret-467") || strings.Contains(logs, "381942") { + t.Fatal("gallery event exposes a secret") + } + }) + } +} diff --git a/feedback_widget.go b/feedback_widget.go new file mode 100644 index 0000000..8002399 --- /dev/null +++ b/feedback_widget.go @@ -0,0 +1,118 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" +) + +// FeedbackKind is what the feedback is about. +type FeedbackKind int + +const ( + FeedbackBug FeedbackKind = iota + FeedbackIdea + FeedbackPraise + FeedbackOther +) + +// Feedback is what a FeedbackWidget sends. +type Feedback struct { + Kind FeedbackKind + Text string +} + +// FeedbackWidgetState holds the draft and its form state. +type FeedbackWidgetState struct { + Draft Feedback + Form FormState +} + +// EndSubmit clears successful drafts and preserves failed ones. +func (s *FeedbackWidgetState) EndSubmit(err string) { + s.Form.EndSubmit(err) + if err == "" { + s.Draft.Text = "" + } +} + +// FeedbackWidgetOptions configure a FeedbackWidget. +type FeedbackWidgetOptions struct { + // Attachments builds the caller-provided screenshot or file slot. + Attachments func() + // MinLength is the fewest characters accepted; zero means 10. + MinLength int + Disabled bool +} + +// FeedbackWidgetResult carries the submitted feedback. +type FeedbackWidgetResult struct { + Element *ui.Element + feedback Feedback + submitted bool +} + +// Submitted returns feedback; finish the request with EndSubmit. +func (r FeedbackWidgetResult) Submitted() (Feedback, bool) { return r.feedback, r.submitted } + +// FeedbackWidget validates feedback and submits it once. +func FeedbackWidget(c *ui.Context, s *FeedbackWidgetState, opts FeedbackWidgetOptions) FeedbackWidgetResult { + if opts.MinLength < 0 { + panic("mujica: FeedbackWidget MinLength cannot be negative") + } + if s.Draft.Kind < FeedbackBug || s.Draft.Kind > FeedbackOther || opts.MinLength > 2000 { + panic("mujica: FeedbackWidget invalid kind or length") + } + minLen := opts.MinLength + if minLen == 0 { + minLen = 10 + } + d := Density(c) + var r FeedbackWidgetResult + r.Element = ui.Column(c).Gap(d.Unit() * 4).Width(420).Disabled(opts.Disabled) + disabled := r.Element.IsDisabled() + if disabled { + frozen := *s + frozen.Form.order = append([]string(nil), s.Form.order...) + frozen.Form.fields = make(map[string]*formFieldState, len(s.Form.fields)) + for name, field := range s.Form.fields { + copy := *field + frozen.Form.fields[name] = © + } + frozen.Form.requested, frozen.Form.awaiting = false, false + s = &frozen + } + locked := disabled || s.Form.Submitting() + r.Element.Children(func() { + kind := int(s.Draft.Kind) + var segs []Segment + for f := FeedbackBug; f <= FeedbackOther; f++ { + segs = append(segs, Segment{Label: msg(c, fmt.Sprintf("feedback_widget.kind%d", f))}) + } + SegmentedControl(c, &kind, segs, SegmentedControlOptions{Equal: true, Label: msg(c, "feedback_widget.kind"), Disabled: locked}) + if !locked { + s.Draft.Kind = FeedbackKind(kind) + } + res := Form(c, &s.Form, FormOptions{SubmitLabel: msg(c, "feedback_widget.send"), ResetLabel: msg(c, "feedback_widget.clear")}, func(f *FormScope) { + FormItem(c, f, "text", &s.Draft.Text, FormItemOptions[string]{Label: msg(c, "feedback_widget.message"), Required: true, + Validate: func(v string) string { + if n := len([]rune(strings.TrimSpace(v))); n < minLen { + return fmt.Sprintf(msg(c, "feedback_widget.short"), minLen) + } + return "" + }}, func() *ui.Element { + return TextArea(c, &s.Draft.Text, TextAreaOptions{MinHeight: 96, MaxLength: 2000, Disabled: locked}).Input + }) + if opts.Attachments != nil { + ui.Column(c).Gap(d.Unit()).Disabled(locked).Children(opts.Attachments) + } + }) + if res.Submitted() && !disabled { + r.feedback, r.submitted = s.Draft, true + slog.Debug("mujica: feedback_widget submitted", "kind", s.Draft.Kind) + } + }) + return r +} diff --git a/feedback_widget_test.go b/feedback_widget_test.go new file mode 100644 index 0000000..f40e405 --- /dev/null +++ b/feedback_widget_test.go @@ -0,0 +1,121 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestFeedbackWidgetValidatesSubmitsOnce(t *testing.T) { + var s FeedbackWidgetState + var sent []Feedback + attached := false + tt := accountTester(500, 500, func(c *ui.Context) { + r := FeedbackWidget(c, &s, FeedbackWidgetOptions{Attachments: func() { attached = true; ui.Text(c, "screenshot slot") }}) + if f, ok := r.Submitted(); ok { + sent = append(sent, f) + } + }) + if !attached || !tt.HasText("screenshot slot") { + t.Fatal("attachment slot not built") + } + _ = tt.Click("Idea") + _ = tt.Click("Message") + tt.Type("short") + _ = tt.Click("Send feedback") + if len(sent) != 0 || !tt.HasText("Write at least 10 characters.") { + t.Fatalf("short sent: %q", tt.Texts()) + } + _ = tt.Click("Message") + tt.Type(" but now long enough") + _ = tt.Click("Send feedback") + _ = tt.Click("Send feedback") + if len(sent) != 1 || sent[0].Kind != FeedbackIdea || sent[0].Text != "short but now long enough" { + t.Fatalf("sent %+v", sent) + } + s.EndSubmit("") + tt.Frame() + if s.Draft.Text != "" { + t.Fatal("sent draft kept") + } +} + +func TestFeedbackWidgetDisabled(t *testing.T) { + s := FeedbackWidgetState{Draft: Feedback{Text: "A long enough message."}} + sent := 0 + tt := accountTester(500, 500, func(c *ui.Context) { + if _, ok := FeedbackWidget(c, &s, FeedbackWidgetOptions{Disabled: true}).Submitted(); ok { + sent++ + } + }) + _ = tt.Click("Bug") + _ = tt.Click("Praise") + _ = tt.Click("Send feedback") + if sent != 0 || s.Draft.Kind != FeedbackBug { + t.Fatal("disabled widget acted") + } +} + +func TestFeedbackWidgetSnapshot(t *testing.T) { + snapshot(t, "feedback_widget", 460, 400, func(c *ui.Context) { + s := ui.Local(c.Root(), "s", func() FeedbackWidgetState { + return FeedbackWidgetState{Draft: Feedback{Kind: FeedbackBug, Text: "The ticker stutters on resize."}} + }) + ui.Column(c).Padding(16).Children(func() { + FeedbackWidget(c, s, FeedbackWidgetOptions{Attachments: func() { + Button(c, "Attach screenshot", ButtonOptions{Variant: Secondary}) + }}) + }) + }) +} + +func TestFeedbackWidgetPendingFreezesDraftAndAttachments(t *testing.T) { + s := FeedbackWidgetState{Draft: Feedback{Kind: FeedbackIdea, Text: "A long enough message."}} + attachments := 0 + tt := accountTester(500, 500, func(c *ui.Context) { + FeedbackWidget(c, &s, FeedbackWidgetOptions{Attachments: func() { + if Button(c, "Attach", ButtonOptions{}).Clicked() { + attachments++ + } + }}) + }) + if err := tt.Click("Send feedback"); err != nil { + t.Fatal(err) + } + if err := tt.Click("Praise"); err != nil { + t.Fatal(err) + } + if err := tt.Click("Message"); err != nil { + t.Fatal(err) + } + tt.Type("modified") + if err := tt.Click("Attach"); err != nil { + t.Fatal(err) + } + if s.Draft.Kind != FeedbackIdea || s.Draft.Text != "A long enough message." || attachments != 0 { + t.Fatal("pending feedback changes its draft or attachments") + } +} + +func TestFeedbackWidgetFailurePreservesDraftAndInvalidLength(t *testing.T) { + s := FeedbackWidgetState{Draft: Feedback{Kind: FeedbackIdea, Text: "A long enough message."}} + tt := accountTester(600, 500, func(c *ui.Context) { FeedbackWidget(c, &s, FeedbackWidgetOptions{}) }) + if err := tt.Click("Send feedback"); err != nil { + t.Fatal(err) + } + s.EndSubmit("Send failed.") + tt.Frame() + if s.Draft.Text != "A long enough message." || !tt.HasText("Send failed.") { + t.Fatal("failed feedback loses its draft or message") + } + accountPanics(t, func(c *ui.Context) { FeedbackWidget(c, &s, FeedbackWidgetOptions{MinLength: 2001}) }) +} + +func TestFeedbackWidgetDisabledKeepsFormState(t *testing.T) { + s := FeedbackWidgetState{Draft: Feedback{Text: "A long enough message."}} + s.Form.Submit() + accountTester(600, 500, func(c *ui.Context) { FeedbackWidget(c, &s, FeedbackWidgetOptions{Disabled: true}) }) + if s.Form.Submitting() || !s.Form.requested || s.Form.fields != nil { + t.Fatal("disabled frame edits form state") + } +} diff --git a/keyboard_shortcuts_list.go b/keyboard_shortcuts_list.go new file mode 100644 index 0000000..4b7fa70 --- /dev/null +++ b/keyboard_shortcuts_list.go @@ -0,0 +1,109 @@ +package mujica + +import ( + "reflect" + "slices" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// ShortcutGroup is a titled group of bindings in a KeyboardShortcutsList. +type ShortcutGroup struct { + Title string + Bindings []ShortcutBinding +} + +// KeyboardShortcutsListOptions configure a KeyboardShortcutsList. +type KeyboardShortcutsListOptions struct { + // Label names the search field; empty uses the built-in one. + Label string +} + +type shortcutsListRow struct{ group, binding int } + +type shortcutsListState struct { + groups []ShortcutGroup + query string + rows []shortcutsListRow + ready bool +} + +// KeyboardShortcutsList displays searchable, grouped shortcuts using visible rows. +func KeyboardShortcutsList(c *ui.Context, query *string, groups []ShortcutGroup, opts KeyboardShortcutsListOptions) *ui.Element { + k := Tokens(c) + d := Density(c) + label := opts.Label + if label == "" { + label = msg(c, "keyboard_shortcuts_list.search") + } + e := ui.Column(c).Gap(d.Unit() * 4).MinWidth(320) + st := ui.Local(e, "shortcuts", func() shortcutsListState { return shortcutsListState{} }) + e.Children(func() { + SearchInput(c, query, SearchInputOptions{Placeholder: label, Label: label}) + shortcutsListPlan(st, groups, strings.ToLower(strings.TrimSpace(*query))) + if len(st.rows) == 0 { + EmptyState(c, EmptyStateOptions{Kind: EmptyNoResults, Query: *query}) + return + } + accountRows(c, len(st.rows), func(i int) any { return st.rows[i] }, func(i int) { + r := st.rows[i] + g := groups[r.group] + if r.binding < 0 { + ui.Text(c, g.Title).Font(theme.SerifFont).FontWeight(600).TextColor(k.Text).Padding(d.Unit()*2, 0).Role(ui.RoleGroup).Label(g.Title) + return + } + b := g.Bindings[r.binding] + ui.Row(c).MinHeight(d.ControlHeight()).AlignItems(ui.Center).Gap(d.Unit()*4). + BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).Role(ui.RoleRow). + Label(b.Action + ", " + b.Shortcut.String()).Children(func() { + ui.Text(c, b.Action).Grow(1).MinWidth(0).Role(ui.RoleNone) + shortcutKbd(c, b.Shortcut) + }) + }) + }) + return e +} + +func shortcutsListPlan(st *shortcutsListState, groups []ShortcutGroup, q string) { + if st.ready && st.query == q && reflect.DeepEqual(st.groups, groups) { + return + } + st.ready, st.query = true, q + st.groups = slices.Clone(groups) + st.rows = st.rows[:0] + for gi, g := range groups { + st.groups[gi].Bindings = slices.Clone(g.Bindings) + header := false + for bi, b := range g.Bindings { + if b.Action == "" { + panic("mujica: KeyboardShortcutsList binding needs an Action") + } + if q != "" && !strings.Contains(strings.ToLower(b.Action), q) && !strings.Contains(strings.ToLower(b.Shortcut.String()), q) { + continue + } + if !header && g.Title != "" { + st.rows = append(st.rows, shortcutsListRow{gi, -1}) + header = true + } + st.rows = append(st.rows, shortcutsListRow{gi, bi}) + } + } +} + +func accountRows(c *ui.Context, n int, key func(int) any, row func(int)) *ui.Element { + if n <= 8 { + return ui.Column(c).Children(func() { + for i := range n { + ui.Column(c).Key(key(i)).Children(func() { row(i) }) + } + }) + } + site := ui.Column(c).MinHeight(0).Shrink(1) + st := ui.Local(site, "rows", func() ui.ListState { return ui.ListState{} }) + st.Key = key + site.Children(func() { ui.List(c, st, n, row).Height(320).MinHeight(0).Shrink(1) }) + return site +} diff --git a/keyboard_shortcuts_list_test.go b/keyboard_shortcuts_list_test.go new file mode 100644 index 0000000..22b1611 --- /dev/null +++ b/keyboard_shortcuts_list_test.go @@ -0,0 +1,106 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +var shortcutGroups = []ShortcutGroup{ + {Title: "General", Bindings: []ShortcutBinding{{"Command palette", Shortcut{ui.Ctrl | ui.Shift, ui.KeyP}}, {"Settings", Shortcut{ui.Ctrl, ui.KeyComma}}}}, + {Title: "Editing", Bindings: []ShortcutBinding{{"Duplicate line", Shortcut{ui.Alt | ui.Shift, ui.KeyDown}}, {"Toggle comment", Shortcut{}}}}, +} + +func TestKeyboardShortcutsListGroupsAndSearch(t *testing.T) { + query := "" + tt := accountTester(500, 500, func(c *ui.Context) { KeyboardShortcutsList(c, &query, shortcutGroups, KeyboardShortcutsListOptions{}) }) + for _, want := range []string{"General", "Editing", "Command palette", "Toggle comment", "—"} { + if !tt.HasText(want) { + t.Fatalf("missing %q in %q", want, tt.Texts()) + } + } + if _, ok := tt.Find("Duplicate line, alt+shift+down"); !ok { + t.Fatal("row not spoken with its keys") + } + _ = tt.Click("Search shortcuts") + tt.Type("comment") + if tt.HasText("General") || !tt.HasText("Toggle comment") { + t.Fatalf("search: %q", tt.Texts()) + } + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("ctrl+,") + if !tt.HasText("Settings") || tt.HasText("Editing") { + t.Fatalf("key search: %q", tt.Texts()) + } + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("zzz") + if !tt.HasText("No results") { + t.Fatalf("empty: %q", tt.Texts()) + } +} + +func TestKeyboardShortcutsListSnapshot(t *testing.T) { + snapshot(t, "keyboard_shortcuts_list", 460, 320, func(c *ui.Context) { + q := ui.Local(c.Root(), "q", func() string { return "" }) + ui.Column(c).Padding(16).Children(func() { KeyboardShortcutsList(c, q, shortcutGroups, KeyboardShortcutsListOptions{}) }) + }) +} + +func TestKeyboardShortcutsListLargeDataBuildsVisibleRows(t *testing.T) { + items := make([]ShortcutBinding, 100000) + for i := range items { + items[i] = ShortcutBinding{Action: fmt.Sprintf("Action %d", i)} + } + query := "" + tt := accountTester(500, 500, func(c *ui.Context) { + KeyboardShortcutsList(c, &query, []ShortcutGroup{{Title: "General", Bindings: items}}, KeyboardShortcutsListOptions{}) + }) + if n := len(tt.Texts()); n > 100 { + t.Fatalf("built %d text elements for a bounded viewport", n) + } + if err := tt.Click("Search shortcuts"); err != nil { + t.Fatal(err) + } + tt.Type("99999") + if !tt.HasText("Action 99999") || tt.HasText("Action 99998") { + t.Fatal("large-list search does not reach last binding") + } +} + +func TestKeyboardShortcutsListDisabledSearchAndInvalidBinding(t *testing.T) { + q := "" + tt := accountTester(600, 500, func(c *ui.Context) { + ui.Column(c).Disabled(true).Children(func() { KeyboardShortcutsList(c, &q, shortcutGroups, KeyboardShortcutsListOptions{}) }) + }) + if err := tt.Click("Search shortcuts"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Type("save") + if q != "" { + t.Fatal("disabled search edits") + } + accountPanics(t, func(c *ui.Context) { + KeyboardShortcutsList(c, &q, []ShortcutGroup{{Bindings: []ShortcutBinding{{}}}}, KeyboardShortcutsListOptions{}) + }) +} + +func TestKeyboardShortcutsListCachedPlanSeesInPlaceChanges(t *testing.T) { + st := shortcutsListState{} + groups := []ShortcutGroup{{Title: "General", Bindings: []ShortcutBinding{{Action: "Save"}}}} + shortcutsListPlan(&st, groups, "save") + if len(st.rows) != 2 { + t.Fatal("missing header or binding") + } + before := &st.rows[0] + shortcutsListPlan(&st, groups, "save") + if before != &st.rows[0] { + t.Fatal("unchanged data rebuilt shortcut plan") + } + groups[0].Bindings[0].Action = "Open" + shortcutsListPlan(&st, groups, "save") + if len(st.rows) != 0 { + t.Fatal("cached list misses in-place binding change") + } +} diff --git a/login_form.go b/login_form.go new file mode 100644 index 0000000..ddd3470 --- /dev/null +++ b/login_form.go @@ -0,0 +1,99 @@ +package mujica + +import ( + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +// LoginFormState holds the form; only OnSubmit sees the password. +type LoginFormState struct { + Account string + Remember bool + password string + tried bool + submitting bool + err string +} + +// EndSubmit finishes a sign-in, shows err and wipes the password. +func (s *LoginFormState) EndSubmit(err string) { + if !s.submitting { + panic("mujica: LoginFormState.EndSubmit without a submit in progress") + } + s.submitting, s.err, s.password = false, err, "" + slog.Debug("mujica: login_form submit ended", "failed", err != "") +} + +// LoginFormOptions configure a LoginForm. OnSubmit is required. +type LoginFormOptions struct { + // OnSubmit alone receives the credentials; then call EndSubmit. + OnSubmit func(account, password string, remember bool) + // AccountLabel replaces the built-in "Email or username". + AccountLabel string + Disabled bool +} + +// LoginForm collects credentials; it never checks or stores them. +func LoginForm(c *ui.Context, s *LoginFormState, opts LoginFormOptions) *ui.Element { + if opts.OnSubmit == nil { + panic("mujica: LoginForm needs OnSubmit") + } + k := Tokens(c) + d := Density(c) + accountLabel := opts.AccountLabel + if accountLabel == "" { + accountLabel = msg(c, "login_form.account") + } + e := ui.Column(c).Gap(d.Unit() * 4).Width(340).Disabled(opts.Disabled) + disabled := e.IsDisabled() + missing := func(v string) string { + if s.tried && strings.TrimSpace(v) == "" { + return msg(c, "form.required") + } + return "" + } + submit := false + var acc, pw InputResult + e.Children(func() { + FormField(c, accountLabel, FormFieldOptions{Required: true}, func() *ui.Element { + acc = TextInput(c, &s.Account, TextInputOptions{Disabled: disabled || s.submitting, Error: missing(s.Account)}) + return acc.Input + }) + FormField(c, msg(c, "login_form.password"), FormFieldOptions{Required: true}, func() *ui.Element { + pw = PasswordInput(c, &s.password, PasswordInputOptions{Disabled: disabled || s.submitting, Error: missing(s.password)}) + return pw.Input + }) + if acc.Changed() || pw.Changed() { + s.err = "" + } + Checkbox(c, &s.Remember, msg(c, "login_form.remember"), CheckboxOptions{Disabled: disabled || s.submitting}) + if s.err != "" { + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Start).TextColor(k.Danger).Role(ui.RoleStatus).Children(func() { + ui.Icon(c, icons.Must("circle-alert")).Size(16, 16).Shrink(0) + ui.Text(c, s.err).Grow(1) + }) + } + b := Button(c, msg(c, "login_form.submit"), ButtonOptions{Loading: s.submitting, Disabled: disabled}) + b.FillWidth() + submit = b.Clicked() || acc.Submitted() || pw.Submitted() + }) + if submit && !disabled && !s.submitting { + s.tried = true + switch { + case strings.TrimSpace(s.Account) == "": + acc.Input.Focus() + return e + case s.password == "": + pw.Input.Focus() + return e + } + s.submitting, s.err = true, "" + slog.Debug("mujica: login_form submitted") + opts.OnSubmit(strings.TrimSpace(s.Account), s.password, s.Remember) + } + return e +} diff --git a/login_form_test.go b/login_form_test.go new file mode 100644 index 0000000..a014e16 --- /dev/null +++ b/login_form_test.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "bytes" + "log/slog" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +// accountLogs records every debug log until the test ends. +func accountLogs(t *testing.T) *bytes.Buffer { + t.Helper() + var b bytes.Buffer + saved := slog.Default() + slog.SetDefault(slog.New(slog.NewTextHandler(&b, &slog.HandlerOptions{Level: slog.LevelDebug}))) + t.Cleanup(func() { slog.SetDefault(saved) }) + return &b +} + +type loginCall struct { + account, password string + remember bool +} + +func loginTester(s *LoginFormState, calls *[]loginCall, disabled *bool) *ui.Tester { + return accountTester(420, 400, func(c *ui.Context) { + LoginForm(c, s, LoginFormOptions{Disabled: *disabled, OnSubmit: func(a, p string, r bool) { + *calls = append(*calls, loginCall{a, p, r}) + }}) + }) +} + +func TestLoginFormSubmitsOnceThroughCallback(t *testing.T) { + logs := accountLogs(t) + const secret = "s3cret-Sakiko" + var s LoginFormState + var calls []loginCall + disabled := false + tt := loginTester(&s, &calls, &disabled) + _ = tt.Click("Email or username") + tt.Type(" sakiko@court ") + _ = tt.Click("Password") + tt.Type(secret) + _ = tt.Click("Remember me") + tt.Key(ui.Shift, ui.KeyTab) + tt.Key(ui.Shift, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + _ = tt.Click("Sign in") + if len(calls) != 1 || calls[0].account != "sakiko@court" || calls[0].password != secret || !calls[0].remember { + t.Fatalf("%d calls", len(calls)) + } + if strings.Contains(logs.String(), secret) { + t.Fatal("the password reached the log") + } + s.EndSubmit("Too many attempts. Try again in 5 minutes.") + tt.Frame() + if !tt.HasText("Too many attempts. Try again in 5 minutes.") || s.password != "" || s.Account != " sakiko@court " { + t.Fatalf("shown %v wiped %v account %q", tt.HasText("Too many attempts. Try again in 5 minutes."), s.password == "", s.Account) + } +} + +func TestLoginFormRequiresBothAndBlocksDisabled(t *testing.T) { + var s LoginFormState + var calls []loginCall + disabled := false + tt := loginTester(&s, &calls, &disabled) + _ = tt.Click("Sign in") + if len(calls) != 0 || !tt.HasText("Required") || !tt.Focused("Email or username") { + t.Fatalf("empty submit: %d calls", len(calls)) + } + tt.Type("uika") + tt.Key(0, ui.KeyEnter) + if len(calls) != 0 || !tt.Focused("Password") { + t.Fatal("submitted without a password") + } + disabled = true + tt.Type("pw") + _ = tt.Click("Sign in") + if len(calls) != 0 || s.password != "" { + t.Fatal("disabled form took input or submitted") + } +} + +func TestLoginFormSnapshot(t *testing.T) { + snapshot(t, "login_form", 400, 330, func(c *ui.Context) { + s := ui.Local(c.Root(), "s", func() LoginFormState { + return LoginFormState{Account: "sakiko@court", err: "Wrong account or password."} + }) + LoginForm(c, s, LoginFormOptions{OnSubmit: func(string, string, bool) {}}) + }) +} diff --git a/messages_account.go b/messages_account.go new file mode 100644 index 0000000..b0884d0 --- /dev/null +++ b/messages_account.go @@ -0,0 +1,123 @@ +package mujica + +func init() { + addMessages(map[string][2]string{ + "settings_layout.title": {"Settings", "设置"}, + "settings_search.placeholder": {"Search settings", "搜索设置"}, + "settings_search.none": {"No settings match", "没有匹配的设置"}, + + "theme_selector.label": {"Appearance", "外观"}, + "theme_selector.System": {"System", "跟随系统"}, + "theme_selector.Light": {"Light", "浅色"}, + "theme_selector.Dark": {"Dark", "深色"}, + + "accent_color_picker.label": {"Accent color", "主色"}, + "accent_color_picker.low": {"Low contrast", "对比度不足"}, + "accent_color_picker.low_note": {"Marked colors are too faint for text on them.", "带标记的颜色对比度不足,不宜承载文字。"}, + "accent_color_picker.chosen_low": {"%s has low contrast; text on it may be hard to read.", "%s 对比度不足,其上的文字可能难以阅读。"}, + + "shortcut_recorder.press": {"Press keys…", "请按下快捷键…"}, + "shortcut_recorder.none": {"none", "未设置"}, + "shortcut_recorder.conflict": {"%s is already used by %s", "%s 已被“%s”使用"}, + "shortcut_recorder.needs_modifier": {"Add Ctrl, Alt or Command; F-keys may stand alone.", "请加上 Ctrl、Alt 或 Command;功能键可单独使用。"}, + + "keyboard_shortcuts_list.search": {"Search shortcuts", "搜索快捷键"}, + + "login_form.account": {"Email or username", "邮箱或用户名"}, + "login_form.password": {"Password", "密码"}, + "login_form.remember": {"Remember me", "记住我"}, + "login_form.submit": {"Sign in", "登录"}, + + "oauth_buttons.continue": {"Continue with %s", "使用 %s 继续"}, + + "two_factor_input.label": {"Verification code", "验证码"}, + "two_factor_input.resend": {"Resend code", "重新发送"}, + "two_factor_input.resend_in": {"Resend in %d:%02d", "%d:%02d 后可重新发送"}, + + "profile_editor.avatar": {"Change picture", "更换头像"}, + "profile_editor.name": {"Name", "名称"}, + "profile_editor.bio": {"Bio", "简介"}, + "profile_editor.unsaved": {"Unsaved changes", "有未保存的更改"}, + "profile_editor.discard": {"Discard", "放弃更改"}, + "profile_editor.save": {"Save", "保存"}, + + "account_switcher.accounts": {"Accounts", "账户"}, + "account_switcher.add": {"Add account…", "添加账户…"}, + + "workspace_switcher.label": {"Workspaces", "工作区"}, + "workspace_switcher.search": {"Find a workspace", "查找工作区"}, + "workspace_switcher.none": {"No workspace matches", "没有匹配的工作区"}, + + "user_menu.label": {"%s, %s", "%s,%s"}, + "user_menu.status0": {"Online", "在线"}, + "user_menu.status1": {"Away", "离开"}, + "user_menu.status2": {"Do not disturb", "勿扰"}, + "user_menu.status3": {"Invisible", "隐身"}, + "user_menu.settings": {"Settings", "设置"}, + "user_menu.sign_out": {"Sign out", "退出登录"}, + + "session_list.label": {"Signed-in devices", "已登录设备"}, + "session_list.current": {"This device", "本设备"}, + "session_list.sign_out": {"Sign out", "注销"}, + "session_list.sign_out_of": {"Sign out of %s", "注销 %s"}, + "session_list.sign_out_others": {"Sign out of other sessions", "注销其他会话"}, + "session_list.confirm_title": {"Sign out everywhere else?", "注销其他所有会话?"}, + "session_list.confirm_text": {"%d other sessions will need to sign in again.", "其他 %d 个会话需要重新登录。"}, + "session_list.now": {"Active now", "当前活跃"}, + "session_list.unknown": {"Last active unknown", "最近活跃时间未知"}, + + "api_key_manager.create": {"Create key", "创建密钥"}, + "api_key_manager.create_title": {"New API key", "新建 API 密钥"}, + "api_key_manager.secret_title": {"Copy your new key", "复制新密钥"}, + "api_key_manager.name": {"Key name", "密钥名称"}, + "api_key_manager.cancel": {"Cancel", "取消"}, + "api_key_manager.done": {"I've saved it", "我已保存"}, + "api_key_manager.once": {"This key shows only once. Store it somewhere safe now.", "此密钥只显示一次,请立即妥善保存。"}, + "api_key_manager.copy": {"Copy key", "复制密钥"}, + "api_key_manager.copied": {"Key copied", "密钥已复制"}, + "api_key_manager.empty": {"No API keys", "暂无 API 密钥"}, + "api_key_manager.revoke": {"Revoke", "撤销"}, + "api_key_manager.revoke_named": {"Revoke %s", "撤销 %s"}, + "api_key_manager.revoke_title": {"Revoke %s?", "撤销 %s?"}, + "api_key_manager.revoke_text": {"Apps using this key stop working at once.", "使用此密钥的应用将立即失效。"}, + "api_key_manager.dates": {"Created %s · last used %s", "创建于 %s · 最近使用 %s"}, + "api_key_manager.never": {"never", "从未"}, + + "usage_quota.amount": {"%s of %s %s", "%s / %s %s"}, + "usage_quota.near": {"Near limit", "接近上限"}, + "usage_quota.over": {"Limit reached", "已达上限"}, + "usage_quota.reset_due": {"Resetting now", "正在重置"}, + "usage_quota.reset_hours": {"Resets in %d h", "%d 小时后重置"}, + "usage_quota.reset_on": {"Resets on %s", "%s 重置"}, + + "onboarding_wizard.progress": {"Setup progress", "引导进度"}, + "onboarding_wizard.count": {"Step %d of %d", "第 %d 步,共 %d 步"}, + "onboarding_wizard.skip": {"Skip", "跳过"}, + "onboarding_wizard.back": {"Back", "上一步"}, + "onboarding_wizard.next": {"Next", "下一步"}, + "onboarding_wizard.finish": {"Finish", "完成"}, + + "coachmark.count": {"%d of %d", "%d / %d"}, + "coachmark.skip": {"Skip tour", "跳过引导"}, + "coachmark.back": {"Back", "上一步"}, + "coachmark.next": {"Next", "下一步"}, + "coachmark.done": {"Done", "完成"}, + + "whats_new_dialog.title": {"What's new", "更新内容"}, + "whats_new_dialog.hide": {"Don't show again", "不再显示"}, + "whats_new_dialog.close": {"Got it", "知道了"}, + "whats_new_dialog.kind0": {"New", "新增"}, + "whats_new_dialog.kind1": {"Improved", "改进"}, + "whats_new_dialog.kind2": {"Fixed", "修复"}, + + "feedback_widget.kind": {"Feedback type", "反馈类型"}, + "feedback_widget.kind0": {"Bug", "问题"}, + "feedback_widget.kind1": {"Idea", "建议"}, + "feedback_widget.kind2": {"Praise", "称赞"}, + "feedback_widget.kind3": {"Other", "其他"}, + "feedback_widget.message": {"Message", "内容"}, + "feedback_widget.short": {"Write at least %d characters.", "请至少写 %d 个字。"}, + "feedback_widget.send": {"Send feedback", "发送反馈"}, + "feedback_widget.clear": {"Clear", "清空"}, + }) +} diff --git a/oauth_buttons.go b/oauth_buttons.go new file mode 100644 index 0000000..b13b08e --- /dev/null +++ b/oauth_buttons.go @@ -0,0 +1,58 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" +) + +// OAuthProvider is a sign-in provider; icons are plain, not trademarks. +type OAuthProvider struct { + ID, Name string + Icon *ui.SVG +} + +// OAuthButtonsOptions configure OAuthButtons. +type OAuthButtonsOptions struct { + // Pending is the provider signing in; the others are blocked. + Pending string + Disabled bool +} + +// OAuthButtonsResult carries the selected provider. +type OAuthButtonsResult struct { + Element *ui.Element + chosen string +} + +// Chosen returns the ID of the provider the user picked. +func (r OAuthButtonsResult) Chosen() (string, bool) { return r.chosen, r.chosen != "" } + +// OAuthButtons offers a button per provider; the caller signs in. +func OAuthButtons(c *ui.Context, providers []OAuthProvider, opts OAuthButtonsOptions) OAuthButtonsResult { + if len(providers) == 0 { + panic("mujica: OAuthButtons needs providers") + } + d := Density(c) + var r OAuthButtonsResult + r.Element = ui.Column(c).Gap(d.Unit() * 2).Width(340).Disabled(opts.Disabled) + disabled := r.Element.IsDisabled() + r.Element.Children(func() { + for _, p := range providers { + if p.ID == "" || p.Name == "" { + panic("mujica: OAuthProvider needs an ID and a Name") + } + label := fmt.Sprintf(msg(c, "oauth_buttons.continue"), p.Name) + ui.Column(c).Key(p.ID).AlignItems(ui.Stretch).Children(func() { + b := Button(c, label, ButtonOptions{Variant: Secondary, Icon: p.Icon, Loading: opts.Pending == p.ID, + Disabled: opts.Pending != "" && opts.Pending != p.ID}) + if b.Clicked() && !disabled && opts.Pending == "" { + r.chosen = p.ID + slog.Debug("mujica: oauth_buttons chosen", "provider", p.ID) + } + }) + } + }) + return r +} diff --git a/oauth_buttons_test.go b/oauth_buttons_test.go new file mode 100644 index 0000000..5d7f0e5 --- /dev/null +++ b/oauth_buttons_test.go @@ -0,0 +1,62 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +var oauthProviders = []OAuthProvider{{ID: "gh", Name: "GitHub"}, {ID: "sso", Name: "Company SSO"}} + +func TestOAuthButtonsChooseAndPending(t *testing.T) { + var chosen []string + opts := OAuthButtonsOptions{} + tt := accountTester(420, 200, func(c *ui.Context) { + if id, ok := OAuthButtons(c, oauthProviders, opts).Chosen(); ok { + chosen = append(chosen, id) + } + }) + if err := tt.Click("Continue with Company SSO"); err != nil { + t.Fatal(err) + } + opts.Pending = "sso" + tt.Frame() + _ = tt.Click("Continue with GitHub") + _ = tt.Click("Continue with Company SSO") + if len(chosen) != 1 || chosen[0] != "sso" { + t.Fatalf("chosen %v", chosen) + } + opts = OAuthButtonsOptions{Disabled: true} + tt.Frame() + _ = tt.Click("Continue with GitHub") + if len(chosen) != 1 { + t.Fatal("disabled buttons chose") + } + defer func() { + if recover() == nil { + t.Fatal("nameless provider accepted") + } + }() + accountTester(100, 100, func(c *ui.Context) { OAuthButtons(c, []OAuthProvider{{ID: "x"}}, OAuthButtonsOptions{}) }) +} + +func TestOAuthButtonsSnapshot(t *testing.T) { + snapshot(t, "oauth_buttons", 380, 130, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { OAuthButtons(c, oauthProviders, OAuthButtonsOptions{Pending: "gh"}) }) + }) +} + +func TestOAuthButtonsKeyboard(t *testing.T) { + chosen := "" + tt := accountTester(600, 300, func(c *ui.Context) { + if id, ok := OAuthButtons(c, oauthProviders, OAuthButtonsOptions{}).Chosen(); ok { + chosen = id + } + }) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if chosen != "sso" { + t.Fatalf("keyboard provider %q", chosen) + } +} diff --git a/onboarding_wizard.go b/onboarding_wizard.go new file mode 100644 index 0000000..98ee3e7 --- /dev/null +++ b/onboarding_wizard.go @@ -0,0 +1,97 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// WizardStep is one page of an OnboardingWizard. +type WizardStep struct { + Title, Description string + Content func() + // Ready, when set, must return true before Next is allowed. + Ready func() bool +} + +// OnboardingWizardOptions configure an OnboardingWizard. +type OnboardingWizardOptions struct { + // NoSkip hides the Skip action. + NoSkip bool +} + +// OnboardingWizardResult reports completion or skipping. +type OnboardingWizardResult struct { + Element *ui.Element + finished, skipped bool +} + +// Finished reports Finish on the last step. +func (r OnboardingWizardResult) Finished() bool { return r.finished } + +// Skipped reports that the user skipped the rest. +func (r OnboardingWizardResult) Skipped() bool { return r.skipped } + +// OnboardingWizard provides progress and step navigation. +func OnboardingWizard(c *ui.Context, step *int, steps []WizardStep, opts OnboardingWizardOptions) OnboardingWizardResult { + if len(steps) == 0 || *step < 0 || *step >= len(steps) { + panic(fmt.Sprintf("mujica: OnboardingWizard step %d outside %d steps", *step, len(steps))) + } + k := Tokens(c) + d := Density(c) + var r OnboardingWizardResult + from := *step + r.Element = ui.Column(c).Gap(d.Unit()*5).Width(520).Padding(d.Unit()*6).Radius(theme.CardRadius). + Background(k.Surface).Border(theme.BorderWidth, k.Border) + r.Element.Children(func() { + head := make([]Step, len(steps)) + for i, s := range steps { + if s.Title == "" { + panic("mujica: WizardStep needs a Title") + } + head[i] = Step{Title: s.Title} + } + at := *step + Steps(c, &at, head, StepsOptions{Label: msg(c, "onboarding_wizard.progress")}) + cur := steps[*step] + ui.Column(c).Key(*step).Gap(d.Unit() * 3).Children(func() { + ui.Text(c, fmt.Sprintf(msg(c, "onboarding_wizard.count"), *step+1, len(steps))).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)) + ui.Text(c, cur.Title).Font(theme.SerifFont).FontSize(FontSize(c, theme.H2Size)).FontWeight(600) + if cur.Description != "" { + ui.Text(c, cur.Description).TextColor(k.TextMuted) + } + if cur.Content != nil { + cur.Content() + } + }) + last := *step == len(steps)-1 + ready := cur.Ready == nil || cur.Ready() + ui.Row(c).Gap(d.Unit() * 3).AlignItems(ui.Center).Children(func() { + if !opts.NoSkip && !last && Button(c, msg(c, "onboarding_wizard.skip"), ButtonOptions{Variant: Ghost}).Clicked() { + r.skipped = true + } + ui.Box(c).Grow(1) + if *step > 0 && Button(c, msg(c, "onboarding_wizard.back"), ButtonOptions{Variant: Secondary}).Clicked() { + *step-- + } + next := msg(c, "onboarding_wizard.next") + if last { + next = msg(c, "onboarding_wizard.finish") + } + if Button(c, next, ButtonOptions{Disabled: !ready}).AutoFocus().Clicked() && ready { + if last { + r.finished = true + } else { + *step++ + } + } + }) + }) + if *step != from || r.finished || r.skipped { + slog.Debug("mujica: onboarding_wizard step", "step", *step, "finished", r.finished, "skipped", r.skipped) + } + return r +} diff --git a/onboarding_wizard_test.go b/onboarding_wizard_test.go new file mode 100644 index 0000000..84ca0da --- /dev/null +++ b/onboarding_wizard_test.go @@ -0,0 +1,94 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestOnboardingWizardStepsSkipFinish(t *testing.T) { + step, name := 0, "" + var finished, skipped int + steps := []WizardStep{ + {Title: "Welcome", Description: "A short tour."}, + {Title: "Your name", Ready: func() bool { return name != "" }, Content: func() {}}, + {Title: "Done"}, + } + tt := accountTester(600, 500, func(c *ui.Context) { + steps[1].Content = func() { TextInput(c, &name, TextInputOptions{Label: "Name"}) } + r := OnboardingWizard(c, &step, steps, OnboardingWizardOptions{}) + if r.Finished() { + finished++ + } + if r.Skipped() { + skipped++ + } + }) + if !tt.HasText("Step 1 of 3") || !tt.Focused("Next") { + t.Fatalf("first step: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + _ = tt.Click("Next") + if step != 1 { + t.Fatalf("not ready but moved: %d", step) + } + _ = tt.Click("Name") + tt.Type("Sakiko") + _ = tt.Click("Next") + _ = tt.Click("Back") + _ = tt.Click("Next") + if step != 2 || tt.HasText("Skip") { + t.Fatalf("step %d", step) + } + _ = tt.Click("Finish") + if finished != 1 || skipped != 0 { + t.Fatalf("finished %d", finished) + } + step = 0 + tt.Frame() + _ = tt.Click("Skip") + if skipped != 1 { + t.Fatal("skip not reported") + } +} + +func TestOnboardingWizardSnapshot(t *testing.T) { + snapshot(t, "onboarding_wizard", 580, 330, func(c *ui.Context) { + step := ui.Local(c.Root(), "step", func() int { return 1 }) + ui.Column(c).Padding(16).Children(func() { + OnboardingWizard(c, step, []WizardStep{{Title: "Welcome"}, {Title: "Pick a theme", Description: "You can change it later in Settings."}, {Title: "Done"}}, OnboardingWizardOptions{}) + }) + }) +} + +func TestOnboardingWizardDisabledAndSingleStep(t *testing.T) { + step, done := 0, 0 + disabled := true + tt := accountTester(600, 500, func(c *ui.Context) { + ui.Column(c).Disabled(disabled).Children(func() { + if OnboardingWizard(c, &step, []WizardStep{{Title: "Only step"}}, OnboardingWizardOptions{NoSkip: true}).Finished() { + done++ + } + }) + }) + if err := tt.Click("Finish"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if done != 0 || step != 0 || tt.HasText("Skip") || tt.HasText("Back") { + t.Fatal("disabled single step acts") + } + disabled = false + tt.Frame() + if err := tt.Click("Finish"); err != nil { + t.Fatal(err) + } + if done != 1 { + t.Fatal("single step cannot finish") + } + step = 1 + accountPanics(t, func(c *ui.Context) { + OnboardingWizard(c, &step, []WizardStep{{Title: "Only step"}}, OnboardingWizardOptions{}) + }) +} diff --git a/overlay_layers.go b/overlay_layers.go index 6b9c419..727db55 100644 --- a/overlay_layers.go +++ b/overlay_layers.go @@ -53,7 +53,14 @@ func overlayEscape(c *ui.Context, key any) bool { // The root is around every focus, so it takes Escape before MyGo's overlays. byFocus := c.Root().Shortcut(0, ui.KeyEscape) byWindow := c.Shortcut(0, ui.KeyEscape) - return byFocus || byWindow + if !byFocus && !byWindow { + return false + } + s := ui.Local(c.Root(), overlayLayersKey{}, func() overlayLayers { return overlayLayers{} }) + last := len(s.keys) - 1 + s.keys[last] = nil + s.keys, s.seen = s.keys[:last], s.seen[:last] + return true } // overlaySiteState follows the focus of a layer after it closes. diff --git a/overlay_test.go b/overlay_test.go index f7ad203..95394fb 100644 --- a/overlay_test.go +++ b/overlay_test.go @@ -98,7 +98,7 @@ func TestOverlayEscapeClosesTopNonModalLayerFirst(t *testing.T) { bottom, top := false, false tt := ui.NewTester(func(c *ui.Context) { Use(c, Settings{}) - if Button(c, "Show", ButtonOptions{}).Clicked() { + if Button(c, "Show", ButtonOptions{}).Absolute().Right(10).Top(10).Clicked() { if bottom { top = true } @@ -107,6 +107,7 @@ func TestOverlayEscapeClosesTopNonModalLayerFirst(t *testing.T) { Overlay(c, &top, OverlayOptions{Place: OverlayTopRight}, func() { ui.Text(c, "Top layer") }) Drawer(c, &bottom, DrawerOptions{Title: "Bottom layer", NonModal: true, Side: DrawerLeft}, nil) }, 600, 400) + tt.SetPreferences(ui.Preferences{ReduceMotion: true, TextScale: 1}) _ = tt.Click("Show") _ = tt.Click("Show") // the overlay opens after the drawer, though built before it if !bottom || !top { diff --git a/profile_editor.go b/profile_editor.go new file mode 100644 index 0000000..52a0caf --- /dev/null +++ b/profile_editor.go @@ -0,0 +1,103 @@ +package mujica + +import ( + "log/slog" + "strings" + "unicode/utf8" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// Profile is what a ProfileEditor edits. +type Profile struct { + Name, Bio string +} + +// ProfileEditorOptions configure a ProfileEditor. +type ProfileEditorOptions struct { + // Saved is the baseline for detecting unsaved edits. + Saved Profile + // Avatar is the current picture; nil shows initials. + Avatar *ui.Bitmap + // BioLimit bounds the saved biography; zero means 160. + BioLimit int + // Saving shows the save in progress and blocks edits. + Saving bool + Disabled bool +} + +// ProfileEditorResult reports save, avatar and discard requests. +type ProfileEditorResult struct { + Element *ui.Element + save, avatar, reset bool +} + +// Save reports a request to store the edited profile. +func (r ProfileEditorResult) Save() bool { return r.save } + +// ChangeAvatar reports a request to pick a new picture. +func (r ProfileEditorResult) ChangeAvatar() bool { return r.avatar } + +// Discarded reports that the edits went back to Saved. +func (r ProfileEditorResult) Discarded() bool { return r.reset } + +// ProfileEditor edits name and biography, marking unsaved changes. +func ProfileEditor(c *ui.Context, p *Profile, opts ProfileEditorOptions) ProfileEditorResult { + if opts.BioLimit < 0 { + panic("mujica: ProfileEditor BioLimit cannot be negative") + } + limit := opts.BioLimit + if limit == 0 { + limit = 160 + } + k := Tokens(c) + d := Density(c) + var r ProfileEditorResult + r.Element = ui.Column(c).Gap(d.Unit() * 4).Width(420).Disabled(opts.Disabled) + locked := r.Element.IsDisabled() || opts.Saving + dirty := *p != opts.Saved + nameErr := "" + if strings.TrimSpace(p.Name) == "" { + nameErr = msg(c, "form.required") + } + r.Element.Children(func() { + ui.Row(c).Gap(d.Unit() * 4).AlignItems(ui.Center).Children(func() { + Avatar(c, p.Name, AvatarOptions{Image: opts.Avatar, Size: 64}) + if Button(c, msg(c, "profile_editor.avatar"), ButtonOptions{Variant: Secondary, Disabled: locked}).Clicked() && !locked { + r.avatar = true + } + }) + FormField(c, msg(c, "profile_editor.name"), FormFieldOptions{Required: true}, func() *ui.Element { + return TextInput(c, &p.Name, TextInputOptions{Disabled: locked, Error: nameErr}).Input + }) + FormField(c, msg(c, "profile_editor.bio"), FormFieldOptions{}, func() *ui.Element { + return TextArea(c, &p.Bio, TextAreaOptions{MaxLength: limit, Disabled: locked}).Input + }) + ui.Row(c).Gap(d.Unit() * 3).AlignItems(ui.Center).Children(func() { + if dirty { + ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).Grow(1).Role(ui.RoleStatus).Children(func() { + ui.Box(c).Size(10, 10).Draw(func(pt *ui.Painter, rect ui.Rect) { + pt.FillPath(internal.Diamond(internal.Snap(pt, rect.X+5), internal.Snap(pt, rect.Y+5), 10), k.Warning) + }) + ui.Text(c, msg(c, "profile_editor.unsaved")).TextColor(k.Warning).FontSize(FontSize(c, theme.CaptionSize)).FontWeight(600) + }) + } else { + ui.Box(c).Grow(1) + } + if Button(c, msg(c, "profile_editor.discard"), ButtonOptions{Variant: Ghost, Disabled: locked || !dirty}).Clicked() && !locked { + *p, r.reset = opts.Saved, true + } + valid := strings.TrimSpace(p.Name) != "" && utf8.RuneCountInString(p.Bio) <= limit + if Button(c, msg(c, "profile_editor.save"), ButtonOptions{Loading: opts.Saving, Disabled: r.Element.IsDisabled() || !dirty || !valid}).Clicked() && !locked && valid { + r.save = true + } + }) + }) + if r.save || r.reset || r.avatar { + slog.Debug("mujica: profile_editor action", "save", r.save, "discard", r.reset, "avatar", r.avatar) + } + return r +} diff --git a/profile_editor_test.go b/profile_editor_test.go new file mode 100644 index 0000000..5188900 --- /dev/null +++ b/profile_editor_test.go @@ -0,0 +1,104 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestProfileEditorUnsavedSaveAndDiscard(t *testing.T) { + saved := Profile{Name: "Sakiko", Bio: "Keyboard."} + p := saved + var saves, avatars, discards int + opts := ProfileEditorOptions{Saved: saved} + tt := accountTester(480, 500, func(c *ui.Context) { + r := ProfileEditor(c, &p, opts) + if r.Save() { + saves++ + } + if r.ChangeAvatar() { + avatars++ + } + if r.Discarded() { + discards++ + } + }) + if tt.HasText("Unsaved changes") { + t.Fatal("clean profile marked unsaved") + } + _ = tt.Click("Save") + if saves != 0 { + t.Fatal("saved without changes") + } + _ = tt.Click("Name") + tt.Type(" Togawa") + if !tt.HasText("Unsaved changes") || p.Name != "Sakiko Togawa" { + t.Fatalf("edit: %q", p.Name) + } + _ = tt.Click("Save") + _ = tt.Click("Change picture") + if saves != 1 || avatars != 1 { + t.Fatalf("saves %d avatars %d", saves, avatars) + } + _ = tt.Click("Discard") + if p != saved || discards != 1 || tt.HasText("Unsaved changes") { + t.Fatalf("discard: %+v", p) + } + _ = tt.Click("Name") + tt.Key(ui.Cmd, ui.KeyA) + tt.Key(0, ui.KeyBackspace) + _ = tt.Click("Save") + if saves != 1 || !tt.HasText("Required") { + t.Fatal("saved an empty name") + } +} + +func TestProfileEditorSavingBlocksEdits(t *testing.T) { + p := Profile{Name: "Uika"} + tt := accountTester(480, 500, func(c *ui.Context) { + ProfileEditor(c, &p, ProfileEditorOptions{Saved: Profile{Name: "Misumi"}, Saving: true}) + }) + _ = tt.Click("Name") + tt.Type("x") + if p.Name != "Uika" { + t.Fatal("edited while saving") + } +} + +func TestProfileEditorSnapshot(t *testing.T) { + snapshot(t, "profile_editor", 460, 400, func(c *ui.Context) { + p := ui.Local(c.Root(), "p", func() Profile { return Profile{Name: "Sakiko Togawa", Bio: "Keyboard, Ave Mujica."} }) + ui.Column(c).Padding(16).Children(func() { ProfileEditor(c, p, ProfileEditorOptions{Saved: Profile{Name: "Sakiko"}}) }) + }) +} + +func TestProfileEditorDisabledAndBioLimit(t *testing.T) { + p := Profile{Name: "Uika"} + disabled := false + saves := 0 + tt := accountTester(600, 500, func(c *ui.Context) { + if ProfileEditor(c, &p, ProfileEditorOptions{Saved: Profile{Name: "Uika"}, BioLimit: 3, Disabled: disabled}).Save() { + saves++ + } + }) + if err := tt.Click("Bio"); err != nil { + t.Fatal(err) + } + tt.Type("初音未来") + if err := tt.Click("Save"); err != nil { + t.Fatal(err) + } + if p.Bio != "初音未来" || saves != 0 { + t.Fatal("over-limit bio is saved or modified") + } + disabled = true + tt.Frame() + tt.Type("edit") + if err := tt.Click("Discard"); err != nil { + t.Fatal(err) + } + if p.Bio != "初音未来" { + t.Fatal("disabled profile changes") + } + accountPanics(t, func(c *ui.Context) { ProfileEditor(c, &p, ProfileEditorOptions{BioLimit: -1}) }) +} diff --git a/session_list.go b/session_list.go new file mode 100644 index 0000000..0dbedbe --- /dev/null +++ b/session_list.go @@ -0,0 +1,124 @@ +package mujica + +import ( + "fmt" + "log/slog" + "slices" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// Session describes a device without carrying its authentication token. +type Session struct { + ID, Device, Location string + LastActive time.Time + // Current marks the session this window runs in. + Current bool +} + +// SessionListOptions configure a SessionList. +type SessionListOptions struct { + Disabled bool +} + +// SessionListResult reports individual and bulk sign-out requests. +type SessionListResult struct { + Element *ui.Element + revoke string + others bool +} + +// Revoked returns the ID of a session to sign out. +func (r SessionListResult) Revoked() (string, bool) { return r.revoke, r.revoke != "" } + +// OthersRevoked reports confirmed bulk sign-out. +func (r SessionListResult) OthersRevoked() bool { return r.others } + +type sessionListState struct { + confirm bool + source []Session + others int + ready bool +} + +// SessionList displays devices and requests sign-out through events. +func SessionList(c *ui.Context, sessions []Session, opts SessionListOptions) SessionListResult { + k := Tokens(c) + d := Density(c) + var r SessionListResult + r.Element = ui.Column(c).Gap(d.Unit() * 3).MinWidth(420).Disabled(opts.Disabled) + st := ui.Local(r.Element, "sessions", func() sessionListState { return sessionListState{} }) + disabled := r.Element.IsDisabled() + if !st.ready || !slices.Equal(st.source, sessions) { + st.source, st.others, st.ready = slices.Clone(sessions), 0, true + ids := map[string]bool{} + for _, s := range sessions { + if s.ID == "" || s.Device == "" || ids[s.ID] { + panic("mujica: Session needs a unique ID and a Device") + } + ids[s.ID] = true + if !s.Current { + st.others++ + } + } + } + others := st.others + r.Element.Children(func() { + ui.Column(c).Role(ui.RoleList).Label(msg(c, "session_list.label")).Children(func() { + accountRows(c, len(sessions), func(i int) any { return sessions[i].ID }, func(i int) { + s := sessions[i] + ui.Row(c).Key(s.ID).MinHeight(d.ControlHeight()+d.Unit()*4).Gap(d.Unit()*3).AlignItems(ui.Center). + BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).Role(ui.RoleListItem).Children(func() { + ui.Column(c).Grow(1).MinWidth(0).Gap(2).Children(func() { + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() { + ui.Text(c, s.Device).FontWeight(600).SingleLine() + if s.Current { + Badge(c, BadgeOptions{Text: msg(c, "session_list.current")}, nil) + } + }) + ui.Text(c, sessionListDetail(c, s)).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)).SingleLine() + }) + if !s.Current { + b := Button(c, msg(c, "session_list.sign_out"), ButtonOptions{Variant: Secondary, Disabled: disabled}). + Label(fmt.Sprintf(msg(c, "session_list.sign_out_of"), s.Device)) + if b.Clicked() && !disabled { + r.revoke = s.ID + } + } + }) + }) + }) + ui.Row(c).Children(func() { + if Button(c, msg(c, "session_list.sign_out_others"), ButtonOptions{Variant: Danger, Disabled: disabled || others == 0}).Clicked() && !disabled { + st.confirm = true + } + }) + }) + if disabled || others == 0 { + st.confirm = false + } + res := AlertDialog(c, &st.confirm, AlertDialogOptions{Title: msg(c, "session_list.confirm_title"), + Description: fmt.Sprintf(msg(c, "session_list.confirm_text"), others), ConfirmLabel: msg(c, "session_list.sign_out_others")}) + r.others = res.Confirmed && !disabled + if r.revoke != "" || r.others { + slog.Debug("mujica: session_list sign out", "others", r.others) + } + return r +} + +// sessionListDetail formats the location and last active time. +func sessionListDetail(c *ui.Context, s Session) string { + when := msg(c, "session_list.now") + if ago := c.Now().Sub(s.LastActive); s.LastActive.IsZero() { + when = msg(c, "session_list.unknown") + } else if ago >= time.Minute { + when = s.LastActive.Format("2006-01-02 15:04") + } + if s.Location == "" { + return when + } + return s.Location + " · " + when +} diff --git a/session_list_test.go b/session_list_test.go new file mode 100644 index 0000000..569ab41 --- /dev/null +++ b/session_list_test.go @@ -0,0 +1,80 @@ +package mujica + +import ( + "fmt" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func sessionSample(now time.Time) []Session { + return []Session{ + {ID: "s1", Device: "MacBook Pro · Safari", Location: "Tokyo", LastActive: now, Current: true}, + {ID: "s2", Device: "iPhone · App", Location: "Osaka", LastActive: time.Date(2026, 10, 1, 9, 30, 0, 0, time.Local)}, + {ID: "s3", Device: "Windows · Edge"}, + } +} + +func TestSessionListRevokeOneAndOthers(t *testing.T) { + var revoked []string + others := 0 + disabled := false + tt := accountTester(600, 500, func(c *ui.Context) { + r := SessionList(c, sessionSample(c.Now()), SessionListOptions{Disabled: disabled}) + if id, ok := r.Revoked(); ok { + revoked = append(revoked, id) + } + if r.OthersRevoked() { + others++ + } + }) + for _, want := range []string{"This device", "Tokyo · Active now", "Osaka · 2026-10-01 09:30", "Last active unknown"} { + if !tt.HasText(want) { + t.Fatalf("missing %q in %q", want, tt.Texts()) + } + } + if _, ok := tt.Find("Sign out of MacBook Pro · Safari"); ok { + t.Fatal("current session offers sign out") + } + if err := tt.Click("Sign out of iPhone · App"); err != nil { + t.Fatal(err) + } + _ = tt.Click("Sign out of other sessions") + if !tt.Focused("Cancel") || !tt.HasText("2 other sessions will need to sign in again.") { + t.Fatalf("confirm: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + if others != 0 { + t.Fatal("Enter on Cancel signed out") + } + _ = tt.Click("Sign out of other sessions") + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if len(revoked) != 1 || revoked[0] != "s2" || others != 1 { + t.Fatalf("revoked %v others %d", revoked, others) + } + disabled = true + tt.Frame() + _ = tt.Click("Sign out of Windows · Edge") + if len(revoked) != 1 { + t.Fatal("disabled list signed out") + } +} + +func TestSessionListSnapshot(t *testing.T) { + snapshot(t, "session_list", 560, 300, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { SessionList(c, sessionSample(c.Now()), SessionListOptions{}) }) + }) +} + +func TestSessionListLargeDataBuildsVisibleRows(t *testing.T) { + items := make([]Session, 100000) + for i := range items { + items[i] = Session{ID: fmt.Sprint(i), Device: fmt.Sprintf("Device %d", i), Current: i == 0} + } + tt := accountTester(600, 500, func(c *ui.Context) { SessionList(c, items, SessionListOptions{}) }) + if n := len(tt.Texts()); n > 100 { + t.Fatalf("built %d text elements for a bounded viewport", n) + } +} diff --git a/settings_layout.go b/settings_layout.go new file mode 100644 index 0000000..a525c60 --- /dev/null +++ b/settings_layout.go @@ -0,0 +1,139 @@ +package mujica + +import ( + "log/slog" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// SettingsPage is one page of a SettingsLayout's navigation. +type SettingsPage struct { + ID, Label string + Icon *ui.SVG +} + +// SettingsLayoutOptions configure a SettingsLayout. +type SettingsLayoutOptions struct { + // Title heads the navigation; empty uses the built-in "Settings". + Title string +} + +// SettingsLayout builds section navigation and independently scrolling content. +func SettingsLayout(c *ui.Context, page *string, pages []SettingsPage, opts SettingsLayoutOptions, content func(id string)) ChoiceResult { + if len(pages) == 0 || content == nil { + panic("mujica: SettingsLayout needs pages and content") + } + found := false + items := make([]SidebarItem, len(pages)) + for i, p := range pages { + if p.ID == "" || p.Label == "" { + panic("mujica: SettingsLayout page needs an ID and a Label") + } + found = found || p.ID == *page + items[i] = SidebarItem{ID: p.ID, Label: p.Label, Icon: p.Icon} + } + if *page == "" { + *page, found = pages[0].ID, true + } + if !found { + panic("mujica: SettingsLayout page " + *page + " is not one of pages") + } + title := opts.Title + if title == "" { + title = msg(c, "settings_layout.title") + } + k := Tokens(c) + d := Density(c) + var r ChoiceResult + r.Element = ui.Row(c).Fill().AlignItems(ui.Stretch).Background(k.Background) + r.Element.Children(func() { + nav := Sidebar(c, page, []SidebarSection{{Title: title, Items: items}}, SidebarOptions{Width: 220}) + if nav.Changed() { + r.changed = true + slog.Debug("mujica: settings_layout page", "page", *page) + } + ui.Scroll(c).Grow(1).MinWidth(0).Padding(d.Unit()*6, d.Unit()*8).Children(func() { + ui.Column(c).Key(*page).MaxWidth(720).Gap(d.Unit() * 8).Children(func() { content(*page) }) + }) + }) + return r +} + +// SettingsSection groups rows under a serif heading. +func SettingsSection(c *ui.Context, title string, rows func()) *ui.Element { + k := Tokens(c) + d := Density(c) + e := ui.Column(c).Gap(d.Unit() * 2).Role(ui.RoleGroup).Label(title) + e.Children(func() { + ui.Text(c, title).Font(theme.SerifFont).FontSize(FontSize(c, theme.H3Size)).FontWeight(600).Role(ui.RoleNone) + ui.Column(c).Radius(theme.CardRadius).Background(k.Surface).Border(theme.BorderWidth, k.Border).Padding(0, d.Unit()*4). + Dividers(theme.BorderWidth, k.Border).Children(rows) + }) + return e +} + +// SettingsRowOptions configure a SettingsRow. +type SettingsRowOptions struct { + Description string + // ID identifies this row for SettingsSearch navigation. + ID string +} + +type settingsRevealKey struct{} + +// settingsReveal queues focus and scrolling for the matching row. +func settingsReveal(c *ui.Context, id string) { + *ui.Local(c.Root(), settingsRevealKey{}, func() string { return "" }) = id +} + +type settingsRowState struct{ at time.Time } + +// SettingsRow associates a label and description with its control. +func SettingsRow(c *ui.Context, label string, opts SettingsRowOptions, control func() *ui.Element) *ui.Element { + if label == "" || control == nil { + panic("mujica: SettingsRow needs a label and a control") + } + k := Tokens(c) + d := Density(c) + row := ui.Row(c).MinHeight(d.ControlHeight()+d.Unit()*4).Gap(d.Unit()*4).AlignItems(ui.Center).Padding(d.Unit()*3, 0) + if opts.ID != "" { + row.Key(opts.ID) + } + st := ui.Local(row, "reveal", func() settingsRowState { return settingsRowState{} }) + reveal := ui.Local(c.Root(), settingsRevealKey{}, func() string { return "" }) + var ctrl, text *ui.Element + row.Children(func() { + text = ui.Column(c).Grow(1).MinWidth(0).Gap(d.Unit()) + text.Children(func() { + ui.Text(c, label).FontWeight(500).Role(ui.RoleNone) + if opts.Description != "" { + ui.Text(c, opts.Description).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)).Role(ui.RoleNone) + } + }) + ui.Row(c).Shrink(0).Children(func() { ctrl = control() }) + }) + if ctrl == nil { + panic("mujica: SettingsRow " + label + ": control returned nil") + } + ctrl.Label(label).Description(opts.Description) + if text.Clicked() && !ctrl.IsDisabled() { + ctrl.Focus() + } + if opts.ID != "" && *reveal == opts.ID { + *reveal = "" + st.at = c.Now() + ctrl.Focus().ScrollIntoView() + slog.Debug("mujica: settings_layout reveal", "row", opts.ID) + } + // A revealed row's face fades from the selection color. + if dur := Motion(c, theme.StateDuration); !st.at.IsZero() && dur > 0 { + if t := c.Now().Sub(st.at); t < dur { + row.Background(k.Selection.Alpha(1 - float32(t)/float32(dur))) + c.AnimationFrame() + } + } + return row +} diff --git a/settings_layout_test.go b/settings_layout_test.go new file mode 100644 index 0000000..44699ef --- /dev/null +++ b/settings_layout_test.go @@ -0,0 +1,103 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +// accountTester runs a themed view inside a padded column. +func accountTester(w, h int, view func(c *ui.Context)) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(10).Fill().Children(func() { view(c) }) + }, w, h) +} + +var settingsPages = []SettingsPage{{ID: "general", Label: "General"}, {ID: "privacy", Label: "Privacy"}} + +func settingsView(c *ui.Context, page *string, sound, track *bool, changes *int) { + if SettingsLayout(c, page, settingsPages, SettingsLayoutOptions{}, func(id string) { + switch id { + case "general": + SettingsSection(c, "Notifications", func() { + SettingsRow(c, "Sound", SettingsRowOptions{ID: "sound", Description: "Chime on new messages."}, func() *ui.Element { + return Switch(c, sound, "", SwitchOptions{}) + }) + }) + case "privacy": + SettingsSection(c, "Data", func() { + SettingsRow(c, "Usage reports", SettingsRowOptions{ID: "track"}, func() *ui.Element { + return Switch(c, track, "", SwitchOptions{}) + }) + }) + } + }).Changed() { + *changes++ + } +} + +func TestSettingsLayoutNavigatesAndLabelsRows(t *testing.T) { + page, sound, track, n := "", false, false, 0 + tt := accountTester(900, 500, func(c *ui.Context) { settingsView(c, &page, &sound, &track, &n) }) + if page != "general" || !tt.HasText("Chime on new messages.") { + t.Fatalf("first page: %q", page) + } + if err := tt.Click("Chime on new messages."); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeySpace) + if !tt.Focused("Sound") || !sound { + t.Fatal("row label does not reach its control") + } + if err := tt.Click("Privacy"); err != nil { + t.Fatal(err) + } + if page != "privacy" || n != 1 || !tt.HasText("Usage reports") || tt.HasText("Sound") { + t.Fatalf("page %q changes %d texts %q", page, n, tt.Texts()) + } +} + +func TestSettingsLayoutRejectsUnknownPage(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("unknown page accepted") + } + }() + page := "nope" + accountTester(400, 300, func(c *ui.Context) { SettingsLayout(c, &page, settingsPages, SettingsLayoutOptions{}, func(string) {}) }) +} + +func TestSettingsLayoutSnapshot(t *testing.T) { + snapshot(t, "settings_layout", 860, 360, func(c *ui.Context) { + st := ui.Local(c.Root(), "snap", func() [3]bool { return [3]bool{false, true, false} }) + page := "general" + ui.Box(c).Fill().Children(func() { settingsView(c, &page, &st[1], &st[2], new(int)) }) + }) +} + +func accountPanics(t *testing.T, view func(*ui.Context)) { + t.Helper() + defer func() { + if recover() == nil { + t.Fatal("invalid options accepted") + } + }() + accountTester(600, 500, view) +} + +func TestSettingsLayoutDisabledNavigationAndControl(t *testing.T) { + page, sound, track, changes := "general", false, false, 0 + tt := accountTester(900, 500, func(c *ui.Context) { + ui.Column(c).Fill().Disabled(true).Children(func() { settingsView(c, &page, &sound, &track, &changes) }) + }) + if err := tt.Click("Privacy"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeySpace) + if page != "general" || sound || track || changes != 0 { + t.Fatal("disabled settings change state") + } +} diff --git a/settings_search.go b/settings_search.go new file mode 100644 index 0000000..5e0cf97 --- /dev/null +++ b/settings_search.go @@ -0,0 +1,188 @@ +package mujica + +import ( + "log/slog" + "reflect" + "slices" + "strings" + "unicode/utf8" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// SettingEntry identifies a searchable setting and its destination. +type SettingEntry struct { + ID, Page string + Label string + Description string + // Keywords match too, as synonyms. + Keywords []string +} + +// SettingsSearchOptions configure a SettingsSearch. +type SettingsSearchOptions struct { + // Limit caps the results shown; zero means 8. + Limit int +} + +// SettingsSearchResult carries the selected destination. +type SettingsSearchResult struct { + Element *ui.Element + chosen *SettingEntry +} + +// Chosen returns the selected entry and reveals its row. +func (r SettingsSearchResult) Chosen() (SettingEntry, bool) { + if r.chosen == nil { + return SettingEntry{}, false + } + return *r.chosen, true +} + +type settingsSearchState struct { + cursor int + source []SettingEntry + query string + limit int + hits []int + ready bool +} + +// SettingsSearch highlights matches and navigates to settings. +func SettingsSearch(c *ui.Context, query *string, entries []SettingEntry, opts SettingsSearchOptions) SettingsSearchResult { + if opts.Limit < 0 { + panic("mujica: SettingsSearch Limit cannot be negative") + } + limit := opts.Limit + if limit == 0 { + limit = 8 + } + d := Density(c) + var r SettingsSearchResult + r.Element = ui.Column(c).Gap(d.Unit() * 2).MinWidth(280) + st := ui.Local(r.Element, "search", func() settingsSearchState { return settingsSearchState{} }) + r.Element.Children(func() { + res := SearchInput(c, query, SearchInputOptions{Placeholder: msg(c, "settings_search.placeholder"), Label: msg(c, "settings_search.placeholder")}) + in := res.Input + q := strings.ToLower(strings.TrimSpace(*query)) + settingsSearchPlan(st, entries, q, limit) + hits := st.hits + st.cursor = max(0, min(st.cursor, len(hits)-1)) + if res.Changed() { + st.cursor = 0 + } + disabled := in.IsDisabled() + before := st.cursor + switch { + case disabled || len(hits) == 0: + case in.Shortcut(0, ui.KeyDown): + st.cursor = (st.cursor + 1) % len(hits) + case in.Shortcut(0, ui.KeyUp): + st.cursor = (st.cursor + len(hits) - 1) % len(hits) + } + if res.Submitted() && len(hits) > 0 && !disabled { + r.chosen = &entries[hits[st.cursor]] + } + if q != "" && len(hits) == 0 { + ui.Text(c, msg(c, "settings_search.none")).TextColor(Tokens(c).TextMuted).Role(ui.RoleStatus) + } + list := accountRows(c, len(hits), func(n int) any { return entries[hits[n]].ID }, func(n int) { + i := hits[n] + if settingsSearchHit(c, entries[i], q, n == st.cursor) { + r.chosen = &entries[i] + } + }) + list.Role(ui.RoleList) + if len(hits) > 8 && (st.cursor != before || res.Changed()) { + ui.Local(list, "rows", func() ui.ListState { return ui.ListState{} }).ScrollIntoView(st.cursor) + } + }) + if r.chosen != nil { + settingsReveal(c, r.chosen.ID) + *query, st.cursor = "", 0 + slog.Debug("mujica: settings_search chosen", "id", r.chosen.ID) + } + return r +} + +func settingsSearchPlan(st *settingsSearchState, entries []SettingEntry, q string, limit int) { + if st.ready && st.query == q && st.limit == limit && reflect.DeepEqual(st.source, entries) { + return + } + st.ready, st.query, st.limit = true, q, limit + st.source = slices.Clone(entries) + st.hits = st.hits[:0] + ids := map[string]bool{} + for i, e := range entries { + if e.ID == "" || e.Page == "" || e.Label == "" || ids[e.ID] { + panic("mujica: SettingEntry needs a unique ID, Page and Label") + } + ids[e.ID] = true + st.source[i].Keywords = slices.Clone(e.Keywords) + text := strings.ToLower(e.Label + " " + e.Description + " " + strings.Join(e.Keywords, " ")) + if q != "" && strings.Contains(text, q) && len(st.hits) < limit { + st.hits = append(st.hits, i) + } + } +} + +// settingsSearchHit builds a marked, clickable search result. +func settingsSearchHit(c *ui.Context, e SettingEntry, q string, current bool) bool { + k := Tokens(c) + d := Density(c) + b := ui.ButtonBase(c).Key(e.ID).MinHeight(d.ControlHeight()).Padding(d.Unit(), d.Unit()*3).Justify(ui.Start). + Radius(theme.ControlRadius).Role(ui.RoleListItem).Label(e.Label).Description(e.Description).Selected(current).Gap(d.Unit() * 2) + internal.Ring(b, k.Focus, theme.ControlRadius) + switch { + case current: + b.Background(k.Selection) + case b.Hovered(): + b.Background(k.SurfaceHover) + } + b.Children(func() { + ui.Box(c).Size(8, 8).Shrink(0).Role(ui.RoleNone).Draw(func(p *ui.Painter, r ui.Rect) { + if current { + p.FillPath(internal.Diamond(internal.Snap(p, r.X+4), internal.Snap(p, r.Y+4), 8), k.AccentText) + } + }) + ui.Column(c).Gap(2).Children(func() { + ui.RichText(c, settingsSearchSpans(c, e.Label, q, theme.BodySize)...) + if e.Description != "" { + ui.RichText(c, settingsSearchSpans(c, e.Description, q, theme.CaptionSize)...).TextColor(k.TextMuted) + } + }) + }) + return b.Clicked() && !b.IsDisabled() +} + +// settingsSearchSpans highlights matches while preserving original Unicode text. +func settingsSearchSpans(c *ui.Context, s, q string, size float32) []ui.Span { + k := Tokens(c) + size = FontSize(c, size) + plain := func(t string) ui.Span { return ui.Span{Text: t, Size: size, Color: k.Text} } + if q == "" { + return []ui.Span{plain(s)} + } + var out []ui.Span + low := strings.ToLower(s) + original := []rune(s) + for { + i := strings.Index(low, q) + if i < 0 { + break + } + start, count := utf8.RuneCountInString(low[:i]), utf8.RuneCountInString(q) + if start > 0 { + out = append(out, plain(string(original[:start]))) + } + out = append(out, ui.Span{Text: string(original[start : start+count]), Size: size, Weight: 700, Background: k.Selection, Color: k.Text}) + original, low = original[start+count:], low[i+len(q):] + } + if len(original) > 0 { + out = append(out, plain(string(original))) + } + return out +} diff --git a/settings_search_test.go b/settings_search_test.go new file mode 100644 index 0000000..81ef93b --- /dev/null +++ b/settings_search_test.go @@ -0,0 +1,122 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +var settingEntries = []SettingEntry{ + {ID: "sound", Page: "general", Label: "Sound", Description: "Chime on new messages.", Keywords: []string{"audio"}}, + {ID: "track", Page: "privacy", Label: "Usage reports", Description: "Send anonymous usage."}, +} + +func TestSettingsSearchFindsHighlightsAndReveals(t *testing.T) { + page, query, sound, track, n := "privacy", "", false, false, 0 + var chosen SettingEntry + tt := accountTester(1000, 600, func(c *ui.Context) { + ui.Row(c).Fill().Children(func() { + ui.Box(c).Width(300).Children(func() { + if e, ok := SettingsSearch(c, &query, settingEntries, SettingsSearchOptions{}).Chosen(); ok { + chosen, page = e, e.Page + } + }) + ui.Box(c).Grow(1).FillHeight().Children(func() { settingsView(c, &page, &sound, &track, &n) }) + }) + }) + _ = tt.Click("Search settings") + tt.Type("AUD") + if _, ok := tt.Find("Sound"); !ok || tt.HasText("Send anonymous usage.") { + t.Fatalf("keyword search: %q", tt.Texts()) + } + _ = tt.Click("Search settings") + tt.Key(ui.Cmd, ui.KeyA) + tt.Type("ou") + if !tt.HasText("ou") || !tt.HasText("nd") || !tt.HasText("Usage reports") { + t.Fatalf("match not split for marking: %q", tt.Texts()) + } + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyEnter) + if chosen.ID != "sound" || page != "general" { + t.Fatalf("chose %q page %q", chosen.ID, page) + } + tt.Frame() + if !tt.Focused("Sound") || query != "" { + t.Fatalf("row not revealed, query %q", query) + } + _ = tt.Click("Search settings") + tt.Type("zzz") + if !tt.HasText("No settings match") { + t.Fatal("no empty result") + } +} + +func TestSettingsSearchSnapshot(t *testing.T) { + snapshot(t, "settings_search", 360, 220, func(c *ui.Context) { + q := ui.Local(c.Root(), "q", func() string { return "us" }) + ui.Column(c).Padding(16).Children(func() { SettingsSearch(c, q, settingEntries, SettingsSearchOptions{}) }) + }) +} + +func TestSettingsSearchHighlightsUnicodeCaseMapping(t *testing.T) { + var spans []ui.Span + tt := accountTester(400, 220, func(c *ui.Context) { + q := "i" + spans = settingsSearchSpans(c, "İstanbul", q, 14) + SettingsSearch(c, &q, []SettingEntry{{ID: "language", Page: "general", Label: "İstanbul"}}, SettingsSearchOptions{}) + }) + if len(spans) != 2 || spans[0].Text != "İ" || spans[0].Weight != 700 || spans[1].Text != "stanbul" || !tt.HasText("İstanbul") { + t.Fatal("Unicode case mapping loses match highlighting") + } +} + +func TestSettingsSearchPlainSpansHaveReadableColor(t *testing.T) { + accountTester(400, 220, func(c *ui.Context) { + for _, span := range settingsSearchSpans(c, "Usage reports", "us", 14) { + if span.Color != Tokens(c).Text { + t.Error("search text does not use the readable Text token") + } + } + }) +} + +func TestSettingsSearchCachedPlanSeesInPlaceChanges(t *testing.T) { + st := settingsSearchState{} + entries := []SettingEntry{{ID: "theme", Page: "general", Label: "Appearance", Keywords: []string{"theme"}}} + settingsSearchPlan(&st, entries, "theme", 8) + if len(st.hits) != 1 { + t.Fatal("initial keyword fails") + } + before := &st.hits[0] + settingsSearchPlan(&st, entries, "theme", 8) + if before != &st.hits[0] { + t.Fatal("unchanged data rebuilt search plan") + } + entries[0].Keywords[0] = "color" + settingsSearchPlan(&st, entries, "theme", 8) + if len(st.hits) != 0 { + t.Fatal("cached search misses in-place keyword change") + } +} + +func TestSettingsSearchDisabledAndLimit(t *testing.T) { + q, chosen := "o", 0 + tt := accountTester(600, 500, func(c *ui.Context) { + ui.Column(c).Disabled(true).Children(func() { + if _, ok := SettingsSearch(c, &q, settingEntries, SettingsSearchOptions{Limit: 1}).Chosen(); ok { + chosen++ + } + }) + }) + if err := tt.Click("Sound"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + tt.Type("edited") + if q != "o" || chosen != 0 || tt.HasText("Send anonymous usage.") { + t.Fatal("disabled or limited search acts incorrectly") + } + accountPanics(t, func(c *ui.Context) { SettingsSearch(c, &q, settingEntries, SettingsSearchOptions{Limit: -1}) }) +} diff --git a/shortcut_recorder.go b/shortcut_recorder.go new file mode 100644 index 0000000..65c4ad2 --- /dev/null +++ b/shortcut_recorder.go @@ -0,0 +1,217 @@ +package mujica + +import ( + "fmt" + "log/slog" + "runtime" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// Shortcut combines modifiers and a key; zero means unassigned. +type Shortcut struct { + Mods ui.Modifiers + Key ui.Key +} + +// IsZero reports an empty shortcut. +func (s Shortcut) IsZero() bool { return s == Shortcut{} } + +// keys returns platform-ordered key names. +func (s Shortcut) keys() []string { + var out []string + mac := runtime.GOOS == "darwin" + add := func(m ui.Modifiers, name string) { + if s.Mods&m != 0 { + out = append(out, name) + } + } + if mac { + add(ui.Ctrl, "ctrl") + add(ui.Alt, "alt") + add(ui.Shift, "shift") + add(ui.Super, "mod") + } else { + add(ui.Ctrl, "ctrl") + add(ui.Super, "Win") + add(ui.Alt, "alt") + add(ui.Shift, "shift") + } + return append(out, shortcutKeyName(s.Key)) +} + +// String spells s as keys joined by "+", as "ctrl+shift+K". +func (s Shortcut) String() string { + if s.IsZero() { + return "" + } + return strings.Join(s.keys(), "+") +} + +// shortcutKeyName is the Kbd name of key. +func shortcutKeyName(k ui.Key) string { + switch { + case k >= ui.KeyA && k <= ui.KeyZ: + return string(rune('A' + k - ui.KeyA)) + case k >= ui.Key0 && k <= ui.Key9: + return string(rune('0' + k - ui.Key0)) + case k >= ui.KeyF1 && k <= ui.KeyF12: + return fmt.Sprintf("F%d", k-ui.KeyF1+1) + } + if n, ok := shortcutNames[k]; ok { + return n + } + panic(fmt.Sprintf("mujica: Shortcut key %d has no name", k)) +} + +var shortcutNames = map[ui.Key]string{ + ui.KeyEnter: "enter", ui.KeyEscape: "esc", ui.KeyBackspace: "backspace", ui.KeyTab: "tab", ui.KeySpace: "Space", + ui.KeyDelete: "delete", ui.KeyHome: "Home", ui.KeyEnd: "End", ui.KeyPageUp: "PgUp", ui.KeyPageDown: "PgDn", + ui.KeyLeft: "left", ui.KeyRight: "right", ui.KeyUp: "up", ui.KeyDown: "down", ui.KeyMinus: "-", ui.KeyEqual: "=", + ui.KeyComma: ",", ui.KeyPeriod: ".", ui.KeySlash: "/", ui.KeySemicolon: ";", ui.KeyQuote: "'", + ui.KeyBracketLeft: "[", ui.KeyBracketRight: "]", ui.KeyBackslash: "\\", ui.KeyBackquote: "`", + ui.KeyInsert: "Insert", ui.KeyContextMenu: "Menu", ui.KeyBack: "Back", ui.KeyForward: "Forward", +} + +// shortcutKbd displays keycaps or a muted unassigned mark. +func shortcutKbd(c *ui.Context, s Shortcut) { + if s.IsZero() { + ui.Text(c, "—").TextColor(Tokens(c).TextMuted) + return + } + Kbd(c, s.keys()...) +} + +// ShortcutBinding names an action that already owns a shortcut. +type ShortcutBinding struct { + Action string + Shortcut Shortcut +} + +// ShortcutRecorderOptions configure a ShortcutRecorder. +type ShortcutRecorderOptions struct { + // Taken lists occupied shortcuts; conflicts preserve the previous value. + Taken []ShortcutBinding + // Label names the recorder, as the action it sets. + Label string + Disabled bool +} + +type shortcutRecorderState struct { + recording, needMod bool + caught *Shortcut + conflict string +} + +// ShortcutRecorder records key combinations; Escape cancels. +func ShortcutRecorder(c *ui.Context, value *Shortcut, opts ShortcutRecorderOptions) ChoiceResult { + if opts.Label == "" { + panic("mujica: ShortcutRecorder needs a Label") + } + d := Density(c) + var r ChoiceResult + r.Element = ui.Column(c).Gap(d.Unit()).AlignItems(ui.Start).Disabled(opts.Disabled) + r.Element.Children(func() { + b := ui.ButtonBase(c).MinHeight(d.ControlHeight()).MinWidth(160).Padding(0, d.Unit()*3).Gap(d.Unit() * 2). + Radius(theme.ControlRadius).Justify(ui.SpaceBetween) + st := ui.Local(b, "rec", func() shortcutRecorderState { return shortcutRecorderState{} }) + disabled := b.IsDisabled() + if disabled || !b.Focused() { + st.recording, st.caught = false, nil + } + if st.caught != nil { + s := *st.caught + st.caught, st.recording, st.conflict = nil, false, "" + for _, t := range opts.Taken { + if t.Shortcut == s && s != *value { + st.conflict = fmt.Sprintf(msg(c, "shortcut_recorder.conflict"), s.String(), t.Action) + } + } + if st.conflict == "" && s != *value { + *value, r.changed = s, true + slog.Debug("mujica: shortcut_recorder set", "shortcut", s.String()) + } + } + if b.Clicked() && !disabled { + st.recording, st.conflict, st.needMod = true, "", false + slog.Debug("mujica: shortcut_recorder recording") + } + if st.recording { + b.HandleInput(func(ev ui.InputEvent) bool { + if b.IsDisabled() { + return false + } + return shortcutRecorderKey(st, ev) + }) + } + shortcutRecorderLook(c, b, st, *value, opts.Label) + shortcutRecorderNote(c, st) + }) + return r +} + +// shortcutRecorderKey captures keys during recording. +func shortcutRecorderKey(st *shortcutRecorderState, ev ui.InputEvent) bool { + switch { + case ev.Kind == ui.InputText: + return true + case ev.Kind != ui.InputKeyDown || ev.Key == ui.KeyUnknown: + return false + case ev.Key == ui.KeyTab && ev.Mods&^ui.Shift == 0: + return false + case ev.Key == ui.KeyEscape && ev.Mods == 0: + st.recording = false + case ev.Mods&^ui.Shift == 0 && !(ev.Key >= ui.KeyF1 && ev.Key <= ui.KeyF12): + st.needMod = true + default: + s := Shortcut{ev.Mods, ev.Key} + st.caught = &s + } + return true +} + +// shortcutRecorderLook displays the shortcut or recording prompt. +func shortcutRecorderLook(c *ui.Context, b *ui.Element, st *shortcutRecorderState, value Shortcut, name string) { + k := Tokens(c) + internal.Ring(b, k.Focus, theme.ControlRadius) + label := name + ": " + value.String() + if value.IsZero() { + label = name + ": " + msg(c, "shortcut_recorder.none") + } + b.Label(label) + bg, border := k.Surface, k.ControlBorder + if st.recording { + bg, border = k.Selection, k.AccentText + } else if b.Hovered() { + bg = k.SurfaceHover + } + b.Background(bg).Border(theme.BorderWidth, border) + b.Children(func() { + if st.recording { + ui.Text(c, msg(c, "shortcut_recorder.press")).TextColor(k.AccentText).SingleLine() + } else { + shortcutKbd(c, value) + } + ui.Icon(c, icons.Must("command")).Size(14, 14).TextColor(k.TextMuted) + }) +} + +// shortcutRecorderNote displays conflict and modifier guidance. +func shortcutRecorderNote(c *ui.Context, st *shortcutRecorderState) { + text := st.conflict + if st.needMod && st.recording { + text = msg(c, "shortcut_recorder.needs_modifier") + } + if text == "" { + return + } + ui.Row(c).Gap(4).AlignItems(ui.Center).TextColor(Tokens(c).Warning).Role(ui.RoleStatus).Children(func() { + ui.Icon(c, icons.Must("triangle-alert")).Size(14, 14) + ui.Text(c, text).FontSize(FontSize(c, theme.CaptionSize)) + }) +} diff --git a/shortcut_recorder_test.go b/shortcut_recorder_test.go new file mode 100644 index 0000000..1d99aad --- /dev/null +++ b/shortcut_recorder_test.go @@ -0,0 +1,124 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +func shortcutRecorderTester(v *Shortcut, opts *ShortcutRecorderOptions, n *int) *ui.Tester { + return accountTester(400, 160, func(c *ui.Context) { + if ShortcutRecorder(c, v, *opts).Changed() { + *n++ + } + }) +} + +func TestShortcutRecorderRecordsAndCancels(t *testing.T) { + v, n := Shortcut{}, 0 + opts := ShortcutRecorderOptions{Label: "Open palette"} + tt := shortcutRecorderTester(&v, &opts, &n) + if _, ok := tt.Find("Open palette: none"); !ok { + t.Fatal("empty value not named") + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if !tt.HasText("Press keys…") { + t.Fatalf("not recording: %q", tt.Texts()) + } + tt.TypeKey(0, ui.KeyK, "k") + if !tt.HasText("Add Ctrl, Alt or Command; F-keys may stand alone.") || n != 0 { + t.Fatal("bare letter accepted") + } + tt.Key(ui.Ctrl|ui.Shift, ui.KeyK) + if v != (Shortcut{ui.Ctrl | ui.Shift, ui.KeyK}) || n != 1 || tt.HasText("Press keys…") { + t.Fatalf("recorded %+v", v) + } + if v.String() != "ctrl+shift+K" { + t.Fatalf("spelled %q", v.String()) + } + tt.Key(0, ui.KeySpace) + tt.Key(0, ui.KeyEscape) + tt.Key(0, ui.KeyF5) + if v.Key != ui.KeyK || tt.HasText("Press keys…") { + t.Fatal("escape did not cancel") + } +} + +func TestShortcutRecorderConflictAndDisabled(t *testing.T) { + v, n := Shortcut{ui.Ctrl, ui.KeyP}, 0 + opts := ShortcutRecorderOptions{Label: "Print", Taken: []ShortcutBinding{{Action: "Save", Shortcut: Shortcut{ui.Ctrl, ui.KeyS}}}} + tt := shortcutRecorderTester(&v, &opts, &n) + _ = tt.Click("Print: ctrl+P") + tt.Key(ui.Ctrl, ui.KeyS) + if v.Key != ui.KeyP || n != 0 || !tt.HasText("ctrl+S is already used by Save") { + t.Fatalf("conflict: %+v %q", v, tt.Texts()) + } + tt.Key(0, ui.KeySpace) + opts.Disabled = true + tt.Key(ui.Ctrl, ui.KeyJ) + if v.Key != ui.KeyP { + t.Fatal("disabled recorder recorded") + } +} + +func TestShortcutRecorderSnapshot(t *testing.T) { + snapshot(t, "shortcut_recorder", 300, 110, func(c *ui.Context) { + a := ui.Local(c.Root(), "a", func() Shortcut { return Shortcut{ui.Ctrl | ui.Shift, ui.KeyK} }) + b := ui.Local(c.Root(), "b", func() Shortcut { return Shortcut{} }) + ui.Column(c).Padding(16).Gap(12).Children(func() { + ShortcutRecorder(c, a, ShortcutRecorderOptions{Label: "Command palette"}) + ShortcutRecorder(c, b, ShortcutRecorderOptions{Label: "Zen mode"}) + }) + }) +} + +func TestShortcutRecorderFunctionKeyAndTabCancellation(t *testing.T) { + v, n := Shortcut{}, 0 + opts := ShortcutRecorderOptions{Label: "Refresh"} + tt := accountTester(400, 200, func(c *ui.Context) { + if ShortcutRecorder(c, &v, opts).Changed() { + n++ + } + Button(c, "Next control", ButtonOptions{}) + }) + if err := tt.Click("Refresh: none"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyF5) + if v != (Shortcut{Key: ui.KeyF5}) || n != 1 { + t.Fatal("standalone function key fails") + } + if err := tt.Click("Refresh: F5"); err != nil { + t.Fatal(err) + } + tt.Key(0, ui.KeyTab) + tt.Key(ui.Ctrl, ui.KeyP) + if v.Key != ui.KeyF5 || n != 1 { + t.Fatal("recorder retains keys after Tab") + } + accountPanics(t, func(c *ui.Context) { ShortcutRecorder(c, &v, ShortcutRecorderOptions{}) }) +} + +func TestShortcutRecorderNamesEveryDeliveredKey(t *testing.T) { + for _, key := range []ui.Key{ui.KeyInsert, ui.KeyContextMenu, ui.KeyBack, ui.KeyForward} { + t.Run(fmt.Sprint(key), func(t *testing.T) { + defer func() { + if recover() != nil { + t.Fatal("recording a valid MyGo key panicked") + } + }() + v, n := Shortcut{}, 0 + opts := ShortcutRecorderOptions{Label: "Action"} + tt := shortcutRecorderTester(&v, &opts, &n) + if err := tt.Click("Action: none"); err != nil { + t.Fatal(err) + } + tt.Key(ui.Ctrl, key) + if v != (Shortcut{ui.Ctrl, key}) || n != 1 || v.String() == "" { + t.Fatal("valid key was not recorded and named") + } + }) + } +} diff --git a/testdata/snapshots/darwin/accent_color_picker-dark.png b/testdata/snapshots/darwin/accent_color_picker-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..99cff8e819fb452c7391e254b1eb1905000036ef GIT binary patch literal 8108 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 SeMNNPk>mj?(v?ys-~I;$0my~` literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/accent_color_picker-light.png b/testdata/snapshots/darwin/accent_color_picker-light.png new file mode 100644 index 0000000000000000000000000000000000000000..7123590eb10debcd0f8ce96bb935b613cc5ea608 GIT binary patch literal 8141 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 KmI6u`2mJ@uhX6VN literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/account_switcher-dark.png b/testdata/snapshots/darwin/account_switcher-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..359d82f3b7bbaa21a70cc982d8b8d68f1f13f6e9 GIT binary patch literal 5659 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 DF<+>8 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/account_switcher-light.png b/testdata/snapshots/darwin/account_switcher-light.png new file mode 100644 index 0000000000000000000000000000000000000000..75c9ae75573e2e6f6ea6aaa7246f12b69ad4389c GIT binary patch literal 5483 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 Y4sqkmq29kvTA~8_(EHl&H6O+N7m#}{ApigX literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/api_key_manager-dark.png b/testdata/snapshots/darwin/api_key_manager-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..dfa634bf6cef8bf5b9a9a4d43b125f13ec537dff GIT binary patch literal 12759 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 LS+Yj_%a8v8T}_A( literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/api_key_manager-light.png b/testdata/snapshots/darwin/api_key_manager-light.png new file mode 100644 index 0000000000000000000000000000000000000000..69434806ab840b253e4751bb4f3222bc7884d9a7 GIT binary patch literal 12332 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 W1=!;VT*X4vgp!d|l&BFi3jQA(tVSRJ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/coachmark-light.png b/testdata/snapshots/darwin/coachmark-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ff58397525832612824711a67ff7fba6c292556b GIT binary patch literal 14937 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 XL(nUFy#@U~kO8E`|Vr8 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 PB2`jUPNYi6!2kaM%Dip2 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/feedback_widget-light.png b/testdata/snapshots/darwin/feedback_widget-light.png new file mode 100644 index 0000000000000000000000000000000000000000..91989d78cd8b5a30b05a73eed0f4a342cfc9a6ec GIT binary patch literal 17776 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/keyboard_shortcuts_list-dark.png b/testdata/snapshots/darwin/keyboard_shortcuts_list-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..bc52177384f823fe0ffc21e7f42ed2f6a6888a21 GIT binary patch literal 17611 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/keyboard_shortcuts_list-light.png b/testdata/snapshots/darwin/keyboard_shortcuts_list-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ebc405401647d9b7e33040c319f67a74a69e2f95 GIT binary patch literal 17007 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/login_form-dark.png b/testdata/snapshots/darwin/login_form-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..00ea5693ba8c4abde1a8336236b6ec35cfd31ce8 GIT binary patch literal 15783 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/onboarding_wizard-dark.png b/testdata/snapshots/darwin/onboarding_wizard-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9569f4560471ad56b41deda1cd03000885821e14 GIT binary patch literal 20777 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 z zt|W$l&(hWwe3knL!C0lx>uR@rU*6bo>3vnl;6@eL-#1x!%{^JLy}7w*{APv)apcri zU41YqDzmW2{x_ziPNG+jQ7eO>WJ;7e$@myu5e>EMFRG`a8n?s&FB5 z7Tol4FfHWv-rxQsJhJyqIsD5N3BySzPFyD=0Kk6qw%qnL9at)^>DeCJTx=61mOtGQ z>Yl;ro!iz{vJiYQboTXIFkSpKnWyvhFyiiBE3KA)*6laOkb<4F9$$#lli zV^GSK6BtDhOL;U+zCwB4Zif0>8q>JmD9L*>qA|tMtM@LvvWfjMB@WPj8$hG;x z=Rqa&q5NW_{Lv4EVfxb_MEwv(!Bi3ustFt*2vIl}vbStEw^=DdAqtG7zV92R*(%rD|GQ4#7f*`}y6LFD>;q1jObP zG560L5v4_TMzKW(RZD!;&(B}7-N$V=e^ixyMM939`W!hjF?gI*&~gmxj8$bUSvdQC z(xHH60G|GY?nqI3iKMlK#qwm#Yl?VvC2pWx1D}5@xI+7+f-otI-4?tZb%mW z`L(+1O@^7n*wioymEc^@l#3*e+scg7^QcXV65;k6<)Da<+hu|}5D|inaDqxp~A^SS7yuY{XSYDRa9A+6xY_fY@QSnBH}80p*x{{e0&a`Y`4$F zFJa^8{J_ej)>b#C+ta<1d6Z)_$w(Y%2$;ZH4!VKWK4E$)YD?kG>~4YhT?yEkohzO7 z)RsZ{uX=l!iEb^Ni($uA8y!dxx%}^tyc;G(gLKve><-s>X@t{+KhP3Jybh?^>uooI z@e%+w8wkG&x}!S0V!xb@vH<0nfoAVdasEF|OuU7x3$Kka*qHhUHpW(9@hEfw|# zOu#$oSQ*_8>NqKn*7mNncsZBu71=`$7Si{}Dft!Z7EUf}4~i$0&=?shv1BbNE<75D z;faxp)|T?Qn@ChFeiwGwHV$`KK5(wgo`_@-%%wc@ie9n!qz4Gb9rba*i(imIT$c z^}ep|!nPn>xRhh`C9peH@x>;g~ zRBVKWDtlV$%GBRdA|cR~4-e``zoaN{z$WV5v1TdcZ2VAeKq_!*U3ur$2JyFfg63vE z+M$)Zx~6|_$!xP~Q99~#gQ6od8``0n<-_g0>U;zQSow7oT(YZ_7CT~;=Xp<$&l1pu zbf(w$li47aT3N|hrmCxQOUv$av!@(wRVV{{rxvzxzdSN+^`hXVNzhQ6CqxtsC@a;f zSM1T6Sb9=HSgmI4@0{fh>_PpqU+x@DgN4vD%c*{ys=SVrq@pA@7r)x-g|txg^eBRP z_L#FL*wYtq5^_H43n>Eccgp`PynONt1|FqzF-u2BU&Bj2!@$fcqD4b{At50lr@z&+ zyB?ca>b9h6*4t`5~S6gmo9)m3FL;$a~D$VX&Rd|A0kyOLBN zUr>CH^eh$S6q@qc-sMz*SV9o(naBTzO! zV2Kz|s0|bqRb_46jit?ge{`0ufX8yGZQFM~^>IGK6@{fis84UcfbjFPJ={AlyEav$ zm64T^Baricd3)Nf^K9sRYJ4M02I=71nESJd<{QS@#>@G$o zVAZ7Y4Z}9Kut-!@IlxTZ{kB2ZtqWlMDU{CtKV58nQlFKF$^Hw1>gG!8ornyf^R0ok zF$p0-zF1Kdp-?Ym*FrL;Q|y3|p)m(1n@AiY#Ke>+|9GXff7i*YcZYi$=OC1DSbncQ z&DT%Tu^QieA-OH&Z)X+{c;4T&hjX^I`A7)~zkc!)VqRY4%PAbtKKkX+7C~H5c26Q5 zFu1&|4xv}8hPiKIXt?9)YE>vkOciEa)gxf8Gjur0v8<*J0%JeOR?m*1^YM@|dcTS04T%BJWnCt(}*ZEQp zp}Pg1D{?4+CZTkn3%oL5N5F7i-o;iH`lB>)WwHPT`5{|Zx0nN4X}PORW|}~qBz%?08B-m zuO;ADP!=002Ls+AKjXf6cZ^F#IUt-@)isZ|D&DTQjzRoxBbflNMB5Nr+T!2fa`vNV zs)Fj`<~;uuu5Z((UP@}S`6>a#@do)Niw_`>)n?D%AffBChlA3^=FZBy$H)GFkVJ98 zKtaAL5L5DXKlsc;Ocf!7+tO5`su~o8R#j0_tGlXewMFq&wN$!}`2D@z+jzG(HxJJz z2(spF>K$8d3hRzP0Es2xY}NWvh+_rfkCOE zWi9m*)wtHc!b>o4^+_0fURDI^z1ru!sXG?yGr;19*{A=cMHP(YW#0ybgU@^oJzdTT zy8QXIjMNMr#}1d=ib)In8VVj)KMx=`vE4Tsw@r@AQ?f&}iG{dXy1RWO}pH!Tg^rxMN37GnbVKI!=zW4I8 zMJAZk(X|UbMi$#(aCmUT?FHX=dMwhi(WH8shtKMbcPG$D!EhRNOMRIpitnA)#gT zJ+_!DKd8h&aDHYx^Ubr2Xl`#8D-DQiY4?+-^9=$O2klluVJ(*-Mlm$fE&Z!4VB1=x zW4xteHL5HfV^o_qer4eJ@R-Bnlss}^4G4_GMyCgJqSpgkj3IeCYmO)3riJA8X6t(= z&DFV?=Uaxus;rItA=%&Ltz-Z)3X-NM=VV~E8A zl){J)qU4Z&L$?<251gR{$T=50qmf@Be$wu6x#5c?q-oJq(NeL$oSigw;3&oakZ_TS zxz0M!7jz$$XjvAvAbVEC)mgYHCZobWKvh zK!LzlxPZsg;o9W;n7M+MPOheFq`U6dXQhuG)W(UML7KTdI!7k3Q>T*AWdty7bXwoY zJ{Ub!-%!)HyYA<0Yt`qMzl>GO+W*YZ*3?~%&zb@A6m}SSto#+wc>k4#i|bj(-0tZysna!~#~ARsV7;2?vV9x; zn%6%07ZBL1#|(66^~%MJRLg(w4J^y(+p=3a9Vz|M_U<0htA~Asv}|?X^Y`~ksO0(` zm6(Bthe+kvj-sYRQQ7YHDFy+qS9?y0h|=Vi-M!W3>G}5Sd9^P)Nn8o0k%Po@%a4C) zoYpj3etX|>{EFw8#dDoCpo~08hEJP1VXTyWTH+<1oP)R4X=o2mOKgnD%)`XQ%8Q$g z*{QQTHsklnmD_r!_d~g5b}z=f+ttGh>B5}OX1&C8>j7;%Ic!MCQAS%)!A$Y!TyfK` zlyYff6yuMLW#qcSGIC%5o!j?=+ zS>E1UT_-}GnWdxdD-}seuR1GxXly{6nik*G;2d_0m$Ld_Ea$XU$b><>tn8coM=}Cq{ne+<$<;Fo@8V09 z7{79Tg})6h8o){Q|Bp)>0U#@^4&I`n96)XVl1?ygEjaXTURyDsRD=|jX zYGQOQD*e_^wI3g5C0mTr0&F~5ih#!~j2DkjlqjSunm7Q~4fNibs5ouQODi75BAMvH znCRVC8x#$$4s_pR&N;NF;_%%&7mfUW=*2cXr6kPN7w8KL`GSOwOUWW-mO91w|9V43 z)l?0Cu_MNEiW-)*46KN6S{r_>WPC$Kj$LN6%VgX+LayXC9)+RGLgWLB&z<(ibT6d+ zAhzTtN2akyr%sygcML+JY%y}Q?MPn(JVFgq270R#H(C6GMa(1y>HckiX;Hs_0roAM z=m_XBw$@H5!(Uy*a9=~=dF5Hux^@3ab>`{sz=G_3jFdE{rnRMN6{p>H`lf*iGxmv= za<3RPs0R_Gu3NB=Y88yxC&s z;qd-T<5WkdJAd!8X`+5eCgMV4YV<@EOC_1edX)~j#YAa@)$rsE4{c;O*_(*XspEZA z)UyG1d?6&%_-Q4sw#Ulujcb()<*6UikaqpS+V*3x=pf5#qr-90Mm<^Adc9!YVgL(Z ziYr{|`Shjo{K6+gO>4_;&AHrdfC`Z0#XL4S@}o|T@8$uYh#;B#=pe2)GOlJ;r|h*0 z_LyG1ZEph>hH|3eh1c8D@Mefd)T{G-+!D_LY4B*OkoEky)$R20@Ulv<>oJLDn}KO? z#-R7_X;05@a`8^@H=Bo##kqNn$|wJxhJpBpKSjmdSrYlZj4sbso}!C?ryf*&?KP6h zk7{Ad%e1%b`Y$dleamTZGtobm=J_UhXPpiRnma#wc{h7)?C&=j>^W@IO?00gz2B?e zwoez0_pm$1qwjH3M5eR2Shp`$h%$klFY757f$uj5tA@E^S9BBebg_s;uPgiC>9mz8 z#vFDw*oTlm{%mxHu-fnWLhyaTqB9744n+;qLx+3{jEyMy9zdhhiwCFNkI-16y_i?k zNZn{r+D5%`Jlp2dxT4{$QC6j5Sw08l{FOS#FAU}jw(uw&v;!Hmn92PY#u6)M=P|Ny zpKd-~P)0(AkxnYg0f^j@^`P7lgq;cm-h-s%b#)6%()Ul;`RpnER zgritcQPHZ>-t3Z@EtbJQVpk%K8#NkSwHc_4M#|#8ABGSZI>Kf~rZe5eD+(XLnlf$%^g8-VdU1L6+WU!f zO~U##q?tzm%1HQO^aehdzmLl67I`&*j`u;5NPbg4vX8EU_Dyx7{(M?HK2bi)QB#?O zjK!OsRnkRtG|C?c9GL{ck>JVVuj1kmptJm-G*d{*JD(X}L&y10U^Ub94CWqM8UaY` z)y)qf&!0G9#!fDorNzALkL6u63k)X@AO0ojyVw31If6rJnwkNe+odJe6B;dS8j*0U zfB$mIu_re!ArygUOqn}!hLazUQO6J{r$QHnW>=d#FTlk-rX!`_ZE4P3h4&~>bVqm zSS<{#hJ>$q2ixqxA_K8dS!Tu^M|!>KGL6P>+2JA^-_sdRoMS5i4e#X&)EX3IK?UIm zA}E)~rjrRuuM6)G81VCZ;N$dNJnj>J{sdecqGIB9SX-{;QIy}yHey(GbS34mX!EfBAOyPjn5R3ULvRy zp_As56q1ob^VLht2N@WmiB5(><|}VL;(G7QA5cHsS@X+Y>+E&bS)aZ4_ncqOIXeQF zAGFoc@9FuL-Ye#9;BkwRJ5Lky5*jluA6UhWU+tflJhI&&Zdx*P@p~e^;Ft|E3a4{I zd;vV?Z%!O%ySR9By7VYUS5+hYOCIT z&^KjW>}smgQ77XWQiI3sY?(WZD_`aktiW$Hs{9kFnloJ;Q(rUj(tG=o-t}slQ;#a! z_b4G#*!J)zhELdmgYXf6_Sy_$Z|fdWtd)&)w^WTC%29v*%K;R1oY zChP7?yk0irfuQ%p_l^Ybj;Uh}Nb`ffDU?SAwzRe5I!waJej~{>ya3ICBudxTD^@mU zwa^ylD0!a{cbIQ`AWn~pj(TF~b7{Jiv*lbiX$Hrsw(C1%K%1xl#g2+tQ6? zvttMU&}%D9ZLj=9tKd~uN~|({I6h6{jipx7PyyD^Yl%5;gOX8#r~zokDZYG}@h%6z zRrkx??P$3(yo{BS5}=z?E4vlj-_aj@6$EwgXV-6btF_{kEw z3HK@L=Npka0`p^fd3bIqV7pNfDKdIEQO&Na)RbZ8b`1ir*H`O0_(E|*Kp7qZBj@Y6 zG2x==O0q*RwKA`~xrZw5EfV#l?hrlj@7>I)(YXr4MYdnP)%b(m@9?&;nvqPoo4 z;lNK1VwSS2%gms2#yx}`8NCk^QjQNbPX>1y+V5E6m&m(cye`ybPyN>NfzMXn?jxoq zG+=H$@kngQPML1SY|prS^?6NITIi@E#IW25IwmO%nX%pC=?aIZ4j=e#8W?|`);g+C zDB`qdQDMvRdHTDXUU$yFd66V&mz>XP>76%EX?DuQ0>yml$Cg|n!-uJ`(& z5yc^E?t<6jIcQcK5l4HbWj1v2%{Q*1EceMUYY}%>LpXEMu$83x~`L zy%uHCJ7m|{EsT=E@5PWnVh7Zo1*(@SK6K>)X44EmHMCF6z~3=V@a*CL%0d*g`&_#t z?Eq=p_|&88GAAD8nFHya+U@^!L0)>?0?b>4W@yikJ~xf%9e3MiyrOG%$T;gRYW?&jhK^cVALIRHURF~!GNPNt z%djJ-MsC*D)J7wDUF~Ym{ZQo^Dj<{H?mb-y*$a{BZ+&v{PIL@FyvAtT@;eE9GISw>o1^}~lx;vYVIl!gBUedF?d z>EDMBid-_{BI=$wr>i3Nzm-1>8!O{+upe7e+v`ugI&jcfDnv>HtYpQqTddlRY5caP zkw-puzg%zbT-^vKQd*5o_I;lC4hPKWMFOz~DI?N+3N6ZLNKBZ)mh_6Pj{)?AW?ch&`B$9&b6~xTXAnT&|j-Q-!eSE zFZbVQ%lho!%HM`}N&)MVl)v*4$L0yDlu2^OofQUupXFp5}I)e6Tt8-s1k6@NJoF z1SydSkU&Wuq>S_LgM>|0K@+rCZtPUrHAQJlEtE@0I4x9_x|X&A66wOEaf4t!;gTAM z2qwMc$vLZiCHwl8FPw7i@8t=3oa#C>92xeS_F04-L!t%#n)t!~`TvW`shtyQhM>Yg-`t4(V`^5g-?isRysN@ zJ1!8)#(8e?ZstE=uxilDdWm8!-gex>2RNMeFJBR2CG$xWm0^NlU?M4{pRftt$)Xcg zCTloaD0Q@x733#xk&(&Z(dQR0=-c)QxmsdO#Yx*e|5lE=k4TURInRFCgBE@SInQckSOw!JnuCK2vYW2d$MFV)?K!mE+tnil2*%#GAY5RU!TXZT*iN{VSF_P+H__wfxl5)>lD zZ?--L+qlvzMi{;mi#&smkBf}lS+@<4(i>*mTX}0tB={xeP&0kMO`ox*=aHRd7n-~VQ+r%=3Km$)COIXQo;=r&uL$u zZ`$F=4E7>dgc2uXrJX>RB+fwt&*^6D?vJuZlD_$uO1;bDm}EMbJcBGZ24b0yzO@N3 zU;W%8vgE0EflsFZ0A(;#Ll(7TkQfNo@=>}LZ^Vdls%&j_Vvq(tj@UXT;L!m24;4Qp zsUjmYDeNgnpNTauu@^{e*ccC+gX1~Cme$`5}jov$5PMlw#<;)GY0_SQ*=@h!1C2Ri*Vj10yU zC|Ce@S7{;gL}?+^gq3Ge&?%;*Qo}_@|5(45!4B1@6$Gb0i>h1j@z)iBYc222Zuw4a zSh;ki?d_lp2EXfKk_2B%tpOI2M}|^ns=SVSNS@$zIOEq-VW3O`&x4PSx2qL(w8RcS zniP2{Sr4oqnh0X#TD#HwOrV*pF<7MTo+RHt;dIO6??Nc`Ba%x|sA9I;cAq)V=b)== z!~OjO;pq|aDUBbd!-{Y3!6DN%aZyp-K3Zd05)y+dJ$as4DKUGWZ}@pmKe@;y^|eJ8 z4N~zp+irAvpS2fl4T=-l*r@%DVrM1K&`E-RXCNO5OCNGfWt=qKUTglv{jA&X%v((j zBMB8-p@$~Acs!$RBp*TBi)UIGEz$MZ`d&ze6Y=sA@2e7*j6xot8v=}oQl1b-^k8J9 zjI>ZnR@V0JZfxNPI0VWqma?_Nfzux1;+!^P$j(1r7n3XS$#l(&`H7s%*DI;3BGQ=+ zAoFXPf1wC~0~FH(98Bz|Q}Dey4X+X|P%d3*5YBuYb;Z$&N>2XltQinEUbWnD_~+r$ z^wVb=QlTI3m=3aX`r&hl>Nq*Ead*GG6;X0EjcArzfaDas<|#cSNslaym7^;xvxzCz z6FJ<&eY53db#*EIGD?cZ-QO8?+SW6^DZ3*pgFee z!spekLa3;zRjhudrfNKT)%?4S4|?kCe3>cmqa@;I(5fWZ7d%MLyk<7+48pu%G3;U1 zt@Ev#F0Bg;B?d`*3ktUsOQ~ z?|sL}N$Y40Dppr+ZfdGBXmBb_54cFdYjJp|I-!Y=S?4ytay80xf(nCyKIpkgS5J=y zP66@ReehgL>ERjnA@(Q0XV)FN<~^A?EA>2y=ss(kBE;|i67=vOKxoYf~1Zl{)j2X5?(K)u?qC+J-lE(1oP9K_EBRMGkQWri(&zey}sOnzpnO zmv`fI@R#^Pv%9^Otfp~-x>v1?LNe${2I-vk~H(qS_Tv)%2 z{pBWe*OjlStSqyiPSEXYf_sZmUPflEIlJb|Fx2RL(^5Z}xekp{l#X)cbRr&V#Wvl9Y`&Ih9_ffiFI$2(fpx?y`_-rnAe zi<;)<`<|U~UBS6=&0M|?@a^9FVQr59Q`6m-m!J!`Y%2~WM8xaK?0}dqC!dULhFFl# zJ><2rGF_|YHNPs>=e51GvNEUF)5_E|_vf4Mcv_w#4`b+#z7~zNXU@YoOAloEk|KWC zsF5{hyefm$m@!=s&5yjextPmjiEB?JC7&{HhPE`(r5p7d7#~-Mzm;LicO3utgwn@% zy^fcM3wlL~IT5YnTk&}ggaq+rn? zP7V&pz#u0Vmw(e9L80w8(5oZ}fWZ5W^cWDAfng$9*U|0YB2BA0YKDLa9`*H;%8d~+ z&={;Z(9+V-czk^9^wxOTlbP+4Fe)wv@$ju;L=IGFIjF0v>vy__e`5_Z7r{D@zfRvg zNcFiVc5K7)INxY)Z2Unwvfd}a!c77R;Bo04kM6c`fPoQFD}kMU@th(#WSIf0%e?nD z+ij^?{mjo-JAc6tCi||hy}aZ&_uw}3`VgLLzNzJ}M)fU!{_XWkM%nW}TKLeQ0qJsn zVj_WD7nYeY$N{@bcRU)gZt%bH1ypprWr@KaqNpI!2w8%-he95=<4SEtA^#WFScs0{ z8)$RA<|j4v=m;bA!Fnd}a5(023#-ilvNkh2yI!{|MQ_)dn`@_n#t$_%vex)XUkP#* z4SI|xZ~iK6Y=jdDb&?SG&@lz`%gJcgakJQ1Tl+r_F3HG@%7eJzf)iO+QW*|eapl$3 zZ#D(LK!$~pP%EpvfzT zG)8m;i3X6L`|HzWZ1y7f2?Y&vQ|AFA=5+$4j)S5k`-0})v@jxktE-1fgYd`4 zwc4k1!(SVEGR_58aSqf!2WOO;#v&to8W{XfwGab6iOHACjdjRlpmXG8^*}0sJrfZ~ zN`e6=4W|6+tU4BCP*)s-E7EULXH^E?cW9lOQWPhj*_ysT%@o$v)m2lQ^W%TU)v44+ zMxNa0xR)Wwrz+atH~5*5nrmud!C=e|A9OI4OLg1eY_@<07fcL3yS?Z&-6J2#+@ZWY zIXOujolRw+rVeRt=5n(yhq+c1AMp}ml@2OwPLXTWs@cl<7x4act*C<5QH^5X#?8%1kvaB{(>bJ=S|HUU z1c!-^uC2NFZeK1V`9a`qM?!AfsG)&_8mK1<;^LxIQzI8p>jW+f2&|LB*EfZ7Unag7 zuH>d~b={LvQ4t0GDj%b-)1DZcx+LB&I_2dPrj%OPIp~5j)G|`mGqp8|f&$6hW3b|F zWm&aQ`}+@UYywOH2?$Ipe||GuO+|d6ckDqiwVjp#cku&}TrjFR*kc(HZW^-jDNAzwt4@PawP*q9hTiZ7#T z2uw^&ZNUei4A|S7TgpOnYgt#XhD(fK=$MIhY;G`u_vX!_?7JGyUk5z4h=SGhO(cTu0agy- z1RYsf?~BX$Sy}a1;xm6wmttNW1$ zlBDSXJU)yFCQix2!~+n01aX{f`-Q@K`$1EXsyR}P5z$nKZA@F>q@4QYIJ1T zGrEwIcwH>~u11ky@n%5?i&L9i!sl4o2BOr-`1tt#Pfj$6mgwF7oC4w&W)H)m8JQ&0 z{N6Xqw0Vh1zmmxAhU^lhT@OybdfXd7KEk%xzpkhmshbu=jjr_T8~igCZ6NC)Z}C6@ zbMEc$XJ?k_1)>lfpa-0beESDQe#N~6mze458keW1Uj~bV=qJ~s)TfO3+eZ+U?TB(a z0^W-9^S6cl^-aY-C->GiXm!qM@P@xm@#}U;Pmh?^Q7dHAXX4JT>>6-%Hh66;{~f8Q zY$VqiHwymd+`V?OT@9(l#)%XUYUdMt^;nAdOkz0*d0!tfB<2wuL_Tik);MOxQ&dVn z-485OH@!Jhp`s#GP*9-P-~;TOpX(_rOBeW9+wA_cf+czU8v47*hO66mR8~eNo6T?P z>4Irwp*9xpq^78-&02#8c(VWc9G}Lh^CoZ&3nS9on+!E5dC4;+*Us0dL;_DY;08K1 zYBHQM)>Lky3F@y&K_QLok|;aDRWIh(Z2%O>E7R&Wcn24Y*k$wx(i0OO=al$)bBMWz>&TL!77Cr1CM?`R$m6ch;u;$Y`HO7t3S<%Cacc?+ z8g>sy`ojxQNaC`H`Ft*a7s+xVmg`CSxN-6D zln`>MJy6zL)cX(9oV;)OGLp#MiKA2R7#~F^z%Da1bp84?@>#Sq+9*?sODu*aY?*^K z(TXJe)xpF3s@|u-dG5b09?qT45Z|eBTYIlZk`j|<2PmrUPm%jI@uZrmqh>%S;-9It z)qZ)2wDg6%bpZgly-Ca=V7J>|J>3?>IyzKAdxeS=p)tr}( zY^-e&&onhQ^7!s8<+VsnPYx-)<~%}s_@ZFq%We&0(^Pin{P?rDCoRGs$e4W3$$-WyZS%2)UxQ8BTf)IuH5Q0$p$4TX3$I^`piUUSIc zSXaRN%A7S_hArl)}aipQ2=oA236TXf*Z-F;%2H1T}pVI@Mt4$!V}z*cOR z6G8lNjZOzSrJ1vfi`@ONWd5d5SO6c-GLcZdFuBQe?`J9K!gF)a?($k>+X+81%813n z59ou6TY6qpg)OwwNj%Q2Z7u)ynfM%APnm=8GXXxg?NbEl|C%15cH8Ud?`1M|TplhZ zfujN7?yL=Ch9>@Pb7s?6rvB}z!~7}NNG<)3*GaYk?%ml6#>|XVTw_ZUK{f!J8Tq+3 zb>)M1o{p2Q=KA96M!IOl=T9s2o@?d)-d_s7Dc6*h(Iwg-kOAg*9ko|*!zQe)tz%;_ zCr-|ivr!b2VP*b3Idt~qM7C;>@*W&-jFkE#CykBKf9h?07DwGLt-$!_sLhC7!9x(J zOsih9cXxMpe2h;%Q~3NRgAV|IJZOUUi|uRd=pSR8Vj-_@w|6IJcQFz*&UrM#msbQ! zEiA{F?tlL5%va&@@$u>E>0Y*b4t(6SSbRSR0vkho9#`m)5|v9AVI!=*H@&1yxaHoU z?HrkY-%d+s{~A4Lg0;x`PC~EhHP>rZPYPwkzH_i@_8UN1Pv?mRnx1}sxm%lrf0qv) zzW{_N3@5nCCe~>&z29BkF4@70dQiN?LkKabe)(nm-Ew_(HmBd${=4{JpmK|y?i8J( zlDFB(Hz#~9{Y$)aYfBE@+>AtMXyD;s5s*;x15f)L7`!DD79WFF@5CXLCZq^D zE+T>oWiFXmM>|pK;qF}SvG;jm(?4(J4>{12BO4O8(7)uk$+3h0Fp4KlsQO053C}>M zlYmKG+~c3s%d*qR#gWfuSsB+{E2cfk?DNV&@!D54V2Tr+1Qba?nj!`3X(!t0Wo6gn zfrHxd1^t458gd!v^#>%TrQ}W4y8S{zrzYSrBo z3$$Et0{u$i{s*Bv2Ay+CYAx?+5O)#POL1 zq1_K}9b|O}aOhWXDx1kaCam|7n7-hp<#xH1_~ff1x=Z`5^rp_Wx&Lf$TG-WDYH+t( zBFc4X*R0wJEdQ%+vGwQaQ(WVq`$7~9dD=JWPx-))*Um~KUHl*YS#!_wiw^&CZk=C; zdqK{_b_4RR1=?^jJ_5hPP5i)P{S5kp^nZNAT@bWc@$<))QU?oDw~f#iC&^iHO10ui z_<4^~$0&TzzYh0UclaRE5iJIjCu7j8`?lz0_iNy{AE@DKz*()D8uhY;yPrQd?e*U3 zMZK2}r!M~fMdW`wZoCimGIZdmtn&V0uH*es#=G!$`QhoL&C83ot?fp)a4N_1IksR} zG(SO{{LyLjD(E-=(N}7qJSby=HPe_q`f1eG%*^b4%jjh)SJ>13Fs`KeRfpbOAshum z)Z0$*U0_c9{E7Tgs*8?U@$JT_S@7}2#W23*agWaXN~g{F@v0>%N>(#%1~Ji@mr&~D zcuBZ%TdBi6t*W}TqU`orXsa=N6e$_+zeenyO3qx`-45!7jkd>#6jx1SGHL<+J$uu# zIjN&?BOJEdxLGSV5>6?d%gmnsX>WV;>>KUuBBzQ+$nyQ@ zchknKf1Ta#!SGS6IC-M_+f^@8a3H{BWMa=t=-+(Ja^M3Fq2LZ7Nyq)MAw;3In8UQTZ(Dymh|>NkLY4ydB<9-w>ZI-xFYaeI4LAw(nTW zNKm6LOW6PwT;$-+HHq)VBvs4d-T~+cdFypM*Y1!d#!n?*ee=5EdG_`xwSwW3P+nI{ z`r9Z}OO?AUYGkVnlk${mB*yGdzQP3;VO7lD;0@kaC&;caXjbSJWD4rhYtf>+@_Fx? zWhwsje*D%{P~+faNZb|1NwFBC4K$gI;51FC!IN5wDWD!q*G#FQ?Wt+laIUPj->Tk- z7rNy4#x3(NnX?9a))Z0A0?1QkDc%7|rE+GtVE}UM=mp)_jvgZij#-x2!v}`qP5ZT> z$@pmTrOY#DK2BXbd3hwV5aSG(+^K!n+5YU3N)rum`PE&tT}O*uz{@C&Jp%E6Tbu^V zfyym#r%6{%Po**?y7?BLFd?(Q)3D;t!>?qY(1uvm%GI~t-tPU|cZi>gEOtBAKaI9I;%47Hq`%h zJXtd(4$X=eEyw6J7Z&E{A;N#c`@iySWErWWi`F~j=J8lerNzeEuC|+x&b|nB_&t;4 z?ei#RXLttePty=zvU2AMs{1KdE2q@xF+_JkF8h|`rNr|8ZuXqvpGm9Ajultw9g!st z4psYoe@zVLl!~9HOBuUrvf=pp_2y!TaYU!_X1DKbT7Up0oT>q|;pTDq4gsRUh$Lhp z1NR`m^GZ_e$3q<$nSXDj14IBqc!Zticc-nbJl;&I+m4>67EGejX0C{EMK^PU{E=RBdVF^@m@rys%DZ)bLb!xUo< z-7|516^tZ_wEO~=-!t~?bG}tSD>!|v(Ne~`Yzv3}0@IPn4W0r&&& z|G?(yIRX&h{f#i{f%*6boJXTb_}=^Ftw1t<0XgI0#@eQd3h)ii&nMx$$Pfgor5VHsFJUJ~MWHQ`&RU-l<$4fM`*Xxx(b6 zR2*Sn|1Hl!A|3z3tA3eCf)&4T3lP_qN&Q`b`g@yk2y)9N*`NhrJS}CS3z9 zt$4mcle%SFY%gFmHglWv{;~2V(%uBA1spGMBvWMS)M|gCSiwa>I%c!W;tL8XS2h-r z%c;J%tgY={1R4cE6hXp9^kwuPQyxVKsAosa7IoIL(PPUXjs-sfu9b#2LQ~J>ki@Wi zphvI+^+aLTZEiJ9R{R!I(F?zDOx!Hs@#%Y?g zw4gwYv=qqtO@KIF)^E=N_fw!Ubt-Sn9uOCa@PzJaG-5C*>vr+&nFIwJof$~pF z^8aYPTkolgwS&VjGdm|IGAh6CqjR19CizLF?4Bc|v-9+}e-$)WLnz=`+F2=Drmjkh zS)>X+J|M)z{I|yeiwZ8bTXQd>EZIq0^yl>n8?n7rmXutqD;P#ZWWmE#p3i0H;<~!J zTE3HzuIcRbIDgA_=qymDclwaao1B!J8x8Pw+hic%svI;XD)Bg5B_Q$mT~q{1Hekjc z9L(L-{GAxGl%=fQ;`f{m`-nq@PM2s3)m6O?``aMcYc$a>sD!d|{89pVnerf0Qw+36 zJL!>T#J0!PElo=fobQ$L@6bo8W%NGNgIvrN3RdwunX=2PIeU&aiw!mj~2 z&dyi0(Zx!62fHVBrlw4oi!zj|sj^iwN7j-N0ea20ejfYB6;w-vgb#-e0mxq;VaQy| zF0QW3-=Lh!9;=_vxuCF;(*4lT7ib#@l1F`Y=8qmc<@!CBl%)9F(?Tu$PA&@f2&Z)2lpZM}MYzqGBgG4P$$*S9Or2lDRpVP$RY>B0N?!CUF4z}I0{-RpDY8I?K? z{HBIbGj<*2za{(TSgj<`t$-3k-m5Dvu6(acE~CP4DVCO&v;cT+ShxVZc2u%G@cz{9 z3L=bXo%V0lG7Yk5X&hZm&;Wo_<@ z$p4taJ6u*ZTFhb*GGqVLh|vOTP8L#IyN$y6S79Oe`3r_SBq6^ zlmt=HHU9!P=F2T7r>1gQjrC{CTK%5I1O-5MS=~<#V=Q}c#q!h*&CSjp0fuXmrM0!v zjA{P@4NU4X0s~2y?tKWpBqYx@K0?(OgFCkK8NeWX{vEjXbvNS zT{==6zmZOxI+-j4o7p>d`cNTjPujMTJcOM5cX@?PiJ}|AyERGMS(!2|6hvpG=Xr6R zA*qN51L*16K(@9yZbD2n+#CJZuvhAT9xHPKl|mfw{^JPz`k>PA@p@|InTAIdjsdU( zf!EhQ{Co!)P=yqYkS!AGtSV706BE0)77RIZZ7(@Qd}@k@gM*8U!%&I00knHHSdXF$ z`}lMu%Q&Idq}?=q@AVD(wL2WY?>K=bN|C`QN*Yp5Us3za-{d9ttEeax%Ae-6b@LP} z=(xSb|1K~+-@@6Z{7sQ4zIS;xn)m9g!gd+lu+mOrYCdSt;m(;VyMOI9HT51YtQzfw zh=>wwEHM5|MJV=ZdshMGzpNgTGbjOgB5VONGdpeBA)=rVtJX||&JPJ0*~#L(yu30= zw&~<+NCI?4buG=Fm!kZ7Yf;&$#1J4fg>j*s8x<|*4q2wajy7la5)>Mr_JS#q#0 z$|e?kZ3V_9jO$uX zN*SgSiwCr<&ne`=gaU>eu@EHw4eNEs$DS7a0FXg%lVIayYpbhQ&RF{Tby-Yg{7iNc zxqmpikuv1M<$QX|_KC)3`D%Q^0XL&y5@a595vh`$pKzb8WfKv^ILrnO7xVk)EaIS7jw(i>q$qQB#wR)-E;z z|AU*vqr&KJYgi2sC$EEo>PLrNXd9~=6zlVrh+qcL)6$3w;^#`YAi6;kF%G0OY z*{3JCb#HRy74iS}%6Cg4I^mmMm3Mb2r*{-oigy`V^@o$$!TpxQQyY_$?Yb{fQDgU7 zRndIJ6?WjRk1AD~&o#O-i+<2d4GfI!qA<4P@lgjJ{Zn57g?^LnYz8C+hTCfT5@T>$wX6Jx&bHGc0y1stB>nK9K<-x>4i zY4r-9uhty#{AlnnpOD2=f`ts@Uq6=V%>QX3$hnCW&8KClh=_?{W>{eQxw^Kdhl$|g z0z^baL@3g;E~%x$*Qh6c@#N0WsCdDQ5UV`F19Nm_a_OG^PfxEj(`t+SK&xz{v<{23mkDcKdD)Q6}Ld+`p%7Fga+1UykYlbm~ zi3$05;#g9Lut76EUWFM;4vTOO7B8=iwU`}pRaDiHe$%&^tOGhK`PoDzc@X^0kOfpvaNQSQ?QQni(w05>vnk8l9*b62q z<~}*8eP^|In{&V`6MP%f@_g97sO^W=(tF;cVn33g4;Q+hZwAnVhvQH-R^w!gI-ibn zCiOl!Y*ia}TGDlkmaH^b5FxEy>x|&k)kX3YSH-k?pP9tx_CGve7=;G7oNxMF5xso; zNbvHyl<>%_rgj(FiG*L3ergJJ=))iNMpDVMC$oSS?)v&mF(FmjZMFmO$>9O*g@wx& z{6Ig^xgy2>7yT*5x%&# zC!`x2_kumh2|q)9F>@+5D8|{fud0V?0&YW{UPD2_4hjQJDR!N9htW&se(GB{wU?H* zN^G)wPtXeGN!hM!bQ>6`xeSj*ogDGxYb7OCOqY-8sg_MFPNfYuoS2&RP^XVGFgQet z#}dXpLa&GU2nq|)a+zWL2lZFtK>ZK&gItG!T27Uk<)z&UwNl5a}8Mk=Pc`tHE&Uw$j%)|gD z;b&rmO*tt3V;2A8jQq_7{;{GRQ%_^cgy83G z9)Hd=XB!uw4r?=lb3jh*s#=dJ4qfLaRwx8vEB&T-^I({5=azNdjz-zD7#wp}7%_%k;Nk=f{Ql#;fcW zfavGf&uuzOW~y{LkIHyfR<~{*BkQ=<*0!B0%F+kaRs0K_nP6epC~>o1nDVm9C)azD zJO3-N(VhR}Ovm}FBiM&=^n_*Nnibj z#^*jGGjp4V*ay-XTXi<(W;06tYj|vS>^Qon$7FSxG=$Qxi_Ym1Vyi^XPp6>;FBcUh z)+ag|G)iZtlEIzM@0LZR!}{~d+gs7shtje)=ptP!BRg9|6F!+`h~3T>JCubg8+Y8& zT5{#A!`+Glzn%%|$owT<9|}-MERyGGAhmhhk_SIEiEZ5#L5qL>d{~;U;Sm;o_l7Kb zxbyJvxi_}Z=ts!3*^Zlpf6&lVkRTwqXr6gRksfXPc}E!i^8A1-vF87N@z+bhleiZWC?R~ ziWRdZ?>G#@7y4w&#AlhXrSf>Q({q{5NiqhvNkgdl6HJuR0?7EtsHwS6@N;`xb8^T@ z5<0v*0Y7a_lyd{&Q?Yf(x!?h4`wQ$e-E?ZB4Ad>?IZm1PIG9R5U-MPWgL~Q!&3uiUi6Q zIa~Oc;7aNudSBBnaxzgbHyS25nCU!PpyJb&_t^g zChw04G^Hxd+gd^TiZ}=`O00h$N0L7Yy$@bo>{namdElHR={mMAJUKY{oa(V){e^T%|&kc+02Woms#1p|ekASEM*>!?Y+oPjEx#)e59_EJ`T zMl~@+ot~5a%1pr=%gNvv0tW9KL;;e<_i5q?&yG)h_qVuOTiXYv3;-98l4=BkYMJWP zsnST;hB69znB5#ajE&9p<&CA<_bpmV)1y5k$)jdy*<9W(ZBB>3+XqdWbYpiPF8jmj zaQ7#-x&&!xX_@HSR2{Q9FyipGGtmCW!-;)3MqIbk7q6%A=0-RUm^p#mT4?Sb$tBU+edh#C7tIX%fw>I{ar}Z(*EM*-``La zMMd#&r^A9Ubum{sgO+?2c%260F37*BKJuptoicF~QyYHc@OJ;w`K^Ep;^5v49Q3Qp zXA&+>jwe_cd27-TWetfzhn7x>oU6L7&h95)RIrM$06&D|}BeQJNOOXmB}uR)0m7dYp^8NrUp8 z4pVk$-!(+JN1hsdrq|@ov^1T5ayXuS_6|eS_`iTP0ECHPzI(9scJ@chhO3fBaSzhc z$j8HrMkxmG<>CiDgORJNBiuu3HgeGg$pza8G%@S1785yij5sNn*fLZ})l-Uu!{PJZ zLx7)wklucx;mggRKNFwNclpHOd7=G$(NU*&D~?9211nko3iQv#kUZ^yC!?{jSfhJt z82G}(B(Jx2G-rWSar3JwmGDu9BhEO?wWkSd*eUk%;BRGG*HES>r(v=pve=#s z`1~JjF0Y?HSyz;wl)FwPE0ZpFy2&{n^*u8&L3P+|8Fq=+E7zcFV`ERQ_;~vW0-@?x zVM5i_%LVZfeB0kWl=~B7WxCr78a9fkre!(2@N@|}!~nqlccerUvQq27X8sT$*cBG0 z?)ucPkmgCOikt}R9g7EBqFG_pD8*&DV|0_*U|z&+;pM|W<=E%cS(ZPeEKzif6DXUX zv6){j7%MA8j*xl<^Ep#51LG~hSaJXuw@aT2^<%SH>&7^Y2ri&WN*{0ZSknx$vJ`rg zf|lCaZ}2+6%x#vM@2jjGgY6?eYSk%2M)LK@sfXvo*Y|o|{;$1)K2g_K!D2Y&DfDo` z#u+sqFOp@|S4*+DxVaf?P^N=X^E@DLwUsx;%B0eAii&cIn@!Kcg3Gq`=rEsN$HaXo z@5d8>(Hj2IHR|+}6B7W@$@?r?%(0kEpig@Tr?s1hR(fuiplGR-z>pCHXrK*U;+HR9 zBF|bc?Ym05m}t#JbY#F#%f?b=7z!Ah9&K|g2=O)j6uYwBhhs!}LNb$JZK?X1w?P{d zfl8RhW9}pyO|(ctV!%GyI0G1M&TXeVP#oKjoG4FUGy^uh7nLpWx7FWz2*c~Xa{<)> zMEyU0gxd8Tv2SJKeT{WsVkeD94muiOMfvZYjrUarN2oW+**{)gD(HA{bvK5d|0P>( zB?7o4)qG`o4GPBj3?4O#I(>ud>cjQ2s@ok$M?!92h5Q-IF!ge%Q6MwUe`Q};dT(hd z`~~=*?8mFOv1I12H=T%+bE)3i(h)T#g~>i=5o?nqhd;J_!9u3XoNY;2cfW4^WsJ$t z9E~Y|<_X8j3I>qZ7nPN%I`UtgOPw!p|2^VBlpF78a{JxfT#jHYsoOrIw`)0pGAe7T z;7$qzHbf{8AUZquY&P7l1qZwdk&XV#n#d$k*HzEScB>Mjq@NmpKSd?k=pLlm0xn|m z^V7#_s%kH<%!s6AS}(gweaTcGW1%}sABEbEf>^NP5)z-sp6R)HsK>=IEOjU4 zCOS1d47o~4z5n@E*DCjpl3A?Su=WP2-dkRF=oWw`MSOkzxL*!9k@~s1>Ubgg^MTXd z-`O*-u|xLEK*mq`QtCpSsORSrxD~3Wj z89)mo-7f3>_NFzDvlu?FW3yqC7TGg}Qn;*Zt253l%!#ov>%A*zFKU_uFrU!M+fq-4E}`*+Jck;A zP_+2E4_>5fQ5L!<$k<9o=s47Pc`n2#9&+Kt?ng=_$a>mWHV4bwrjCDR=+_mUO#pRU zl}l7}c0;VTFRy-t{V~lB__2}=Z!~ZAxWB~|^eqa?6nNCc>hpx4sQO7$CbFZ%!b3|d zT>9pU!A5e(G49RaQ_{!LA7;hLNCLoQfrdlM1x<~O3I|+#d}mM{6wt-R?7;|jsncWi z6gC!gZOo311y6!}d=fcH0Y@|dPY{L=XgZQdfH-^@NDuR>Zo-O-h#i{93xL0=q2p0` z)KOA@%jGQXe!j62ed_^JPJn?}70V|t}f6J{QNLl2|^<0YMp5s;r*i-an_(gUQquano3FUdY9(c|7och~b^*+1QLS6AN)&0D5h6Ve6k7^POD3LCEI-c*p6YD8!5m$E8U(`=1*LKd`{6mWBui`ML0TjYwqd6|;k)AEy`0G`XU4W*Wi zM%wp#xvt5lW&X8-tyr0W&bWxMDZe#o!vIBH{(%bQP95^fM(1j)66f&uWY{mIdk`VGIZnM%7Xsj z^IY}GMt6p!yzoxO?cfYyBzL^t@e3fZRgTm`hL!C1D?2(p|%D3cEvK#p{ zn8wMn8hjq$+f`@WD+rxmwM*gq32K1(`Bd=l)+?SLJrLtCHhO2vR(%Mqc9c`E=OndB znGUR(gtAC%n$*1Nw@cAW@dlH54RYLdN|VSa$r+V-+Rq<7!k}hOmSLZunk+I z9Zd}>z%jX>BM}9Ju*^8?#!Alp{hN{L#YVMX*>aDYw(y#@db@HsI;<$4jg8rCDdOcN zp@MGdjyqFcB5Lk4_X z0*SiLym~5{y-xa&%!#XYeph7u%*(g27{E@C-NH<5X@g3~lzmK-vzz-p0ho7XX++!1 z@^e|W0?L-z;f%-2BD~~zr{yJN(M8<=LA1Cr~K^(lI!q_x%4mF35zO{{^yyD!@OO~Og6U$S zfkvo@9JBepw0YUg<^5YCQR5-TtIU<#h_~W+>{fe{pL6;e`(1DNkWJq!oi0kU>Pd7TWymck7*ueeaO{l~vR!zE1 zy14-ze^yq?p#4ZYUWm6NwFSKQCS64Q_;Km)FXS3$q@t;wyMlR0(ludm)|Wl3mdC}1 zE=suNlGn|h1(;o_%tiqVF{eIK(Vdu_3O=>P5@z{OQy95NG6&=Kxp&scM-)}e)vhET zl=yDr?dkbmdYdkYy>ft`nkCe#cCOOH!C&$s6Gt3BmUKLq3;SQShANW6_eC#j>!A{G ziId~u;~wQD)Jm19kEzamB|}5A!DiC+KU7T2%(Ob$U>62=ErXtE;cS zx@_r5efUhk9Zx7!EKCW3_@NTZ*EX~u>a^~Gzj$hwEDmu9RzB~Bpx#ob>L{0YR8EYg zO4_GO+ABS;ZGKvz^4c~K9Vw-1m^icb@6`B*#xo?bBZEU4LMB?rM)aW}?uQPlg^!!s z^=PV|9Lq*&O(pXqGlSpZCPXpd2xKbbX4)xgv5a~e;0rb)*yFr8L{j18%GJg;NYrwD z&Yn2mL2Zjm`<0Eh5px9E)8j7CH*|Y_inUp)zdIMkCGHL~*NHTXsH+z_Y+(%b+19CLk>-_Q8l;&XLwubgaQR^}c%H|gl7 z6Y-xozOA2?=DN4!E1#9pr$d)!UD_nLP`;OIzGcCr?`MwW9-Qw!Goq|q;)Pwts{3mv zXR<$Eml<~DcWzA1ou|O{V{e;_+7fH7wchyl*7Z6=+MeS*i8EJLZ|69=sJS@oSkH{2 z7mq{pXU<~zUu$gZFVU9B@}}kOL?-3M$=&9Hec6|vd&sc!C$PMk-pIW~aK05^-Szap zN>yx*0&&14iC3;{Ss9$>H`_`(Ej>nkqlIE(e|NX>VT0G^YtEhC{nobZ{B`yTI! zw$I7V7T#W+UK;A_aA3-W)`xO(@7G0bee*J?MSz`s_Y2*?%uLDOLKRoGt*c#h(a!LV z`b#TmC3&akrKKMJ z>A(L)+TF*0cRIFAxN}*Of8Y7n=4CscAK6&4_kQM#wpo{+FN>S^yVWvp-r<)o&bnN^ zvEb3HhNRZ;WDeN`2{lJ z(b4I7dDm{Oxo^hH+ncm;7&vMtgd8@~1 zW^<=}`5?aZf!ng?)xV1;zexMjndkN^AW>Y~eERak^F!BOf5y>NvcrJq^4{I7vbMs) zlh=LuU_MD~>Mqx%%NEZ+aXyu2?a%yurvLx@+0Pez5$p0^sY?9kwcQ;GZ>QepPJ3Tt z&>^P;TwuL_>jv5P+KYk9^0>a{kiGt)fmIsUlh z!g^lFGQahr#=*tio2peyt{en*wr<`{0&=I8pwF_Gxkg;{v^Yb6~u`nqEH88&CT`a7B`hg^4xKQ7<@UZQ*ca`QJmg4lu zUCZYGeYtRj`QzEu=E_aL(rogFCwF^pq#ZF+-X^*6?2Ui7dpJJLVur6dB;A(-wH|-- zw_{+dkHw#nXj|A0S`Yu;*2eMS|ARe`x0JL1msS3$J6w3HuWRPS3qMXiJ`NSSyeLeP zv-jl+=R~!4ca{eJoX`ha+<8BL)~nY?=I*|5adwZOZN=4+cUiXV zy|}|^VZaK$SefOyfvbUPE z*N>ph4m8=;v0iv{{pNUfBhapwLx0wO&Nyx{bE4!Z!W#lNozr+3cn>rPSx(jo1%;fa LtDnm{r-UW||7S*h literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/profile_editor-dark.png b/testdata/snapshots/darwin/profile_editor-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0eafde2f5a052979decc8133171db4cb0452e140 GIT binary patch literal 20695 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/session_list-dark.png b/testdata/snapshots/darwin/session_list-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..919b7259a331d0b2e7159f8d0983cc4b670305b3 GIT binary patch literal 23319 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 a44wj3ac@V(IvL%5cP%L@CsGd5_y1q?dIO{Y literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/settings_layout-light.png b/testdata/snapshots/darwin/settings_layout-light.png new file mode 100644 index 0000000000000000000000000000000000000000..6ccf9845e16a06cf00be2317d1f0118b4bc26efe GIT binary patch literal 15917 zcmeHtbx>SOw=X0iOn~472s#9JcL{@AfI))0ySpU7Ai>=sxDEuj;O@a4fYcs1x_0lW>Am*qU;kG3S{F*xYtsWqF^|<=UQ-Z z@MeH#uqQ)j*6?s}0_Req!pd%G`_oykKQUvADT44`gc$ht!(B%xsw++?-{YiEK51Pn zzuf)>B_8LDe`?C)${?50n8X)}A(wLZ2T=$7UH&gwpksqNjhb*j9Z^kbW~FzOf*c6c zkE00g!^Ed06?s859fITi`F(TEC5x2Gla{dc zW^E@jiZJu|*oW5s1q?2*>s~l61lN~_smhTN0by0VFsrCR?*@GK`FTjJfO}lZfc0iX zDmw;{9361D9KuY%+-4E=8bp1uEWoQhWgI{-_9g}-i3Fqx$VLC>O@}-=4R(mAaKUI4 z&E)3Z45N0{+skWx>h>XO^u!z^OS41R&dxYnxOmBUeB69jTr44>Uobqaa zO^quuOgSqE5a;IJG<(Dk4!C4;XF(>s#z!MpMo6}{ZfTiX>%T>b7c_*~g+{idViRn$ zELD{&w&u=q7k~LO_X%F~=&RSpxPnS{7F2@JdtM8<$$6u%+{z`k`QazAzWeOOLSmT~ z8d6Kl1sS3UH7PSWEA2>Em!!-da`u$&B>rD;u@A|O5+!3$Z>GHtdumW>m3{-2L_3*0 zW>BDxgF!^6$%%)ClaYoqLcBo!GL6$C&9f)gsTj$*hu$`ZHDqBv1rKXsWPOg0Cy$~4 zAeMciMENeDC+z9;Fg^H@lJebBFXoKr@b^5K+tidFJLfx^b##M+mL-5+c7)&us6L`Txn!yvBq9!HD7(^PB}O=71BLm zT;YI)b;KpJJ>$?|{b=e#$@nAnzHa3ARQr^Mb^%;OmcKu@4fxSRw6J0xaNYWOdb$aX zxR9D|aKs~;FcaN}Vsd=GxVHGq%81Z6%ewFC*3RMNM9O#!MAiXO5oM^L!m6CA=;4H} zE?atT<9CHvy+Dw(lsL6V)w~@L#R5F5t~v~ zr9+v{s~^+TAi(R&3VK@FhXb#|_oQ|XmAcHMdj}PDY40TD*Ut>NN_9WPkBI9r?+gqK zJo^mS$xIn5%r0s=@{O0R6|t@ES?9>HH}Jn8*6qUOudXB1^QDubYemL zOt6VXZ1@Am*f?39Elpedb$A#`c$l)Xa)(Gc=j4o~g*k^~ig(LO5KuOO2D6pEsgX%! z_t!5n9hc{wMz&o~yBE9u=(KLhrg~OiC;Z*Dsn-y>!)8GcNYkWkoj*z8zCo zs`jrSY?A1TN4hxM+aF(B=y-ek8?`llZZe>oDYRMZ&mBc%C7ZAGn$z6)CV~`x#%D_G2A>Cmx%=dbLy~_WM(OjA$mO$C8_j6y0{Fs#+yY$ckB&6HC?e1{9nL1d3Z~IXQN9 zw3OLn`_p(_QNzVW01e^zDk_R6t-!=vJiMfoR9_x$FGCoZ~H@KFED{PRU1t8D-DbfXrU$=b1lC5iAn>Z>|A70ok%)411EY{XCq|O)_Z;Fa`O-@e{yuDodW|Q1> zC)rPdZeg(|D&C!!C(!SfAXYH;QZ1?3L&fX#8VLK)HP!zLt|uWHQNU|QM@JWDYb&$1 zxVV-d9_hWZSC^;fDgB!c_$YykXR^b?t#f;HZ$eMzw6XZ}Wsj5mB1!M|SE#K0@@22m zShYFNw{}>E#*Fbmg%zKPU_>+2VX(L%C3b^Q!@-ebwTJ=ozQe!WUm3LBHk8K0;mo-S z&>@g zsYbJNus&p5w|8`)0G)&`qN^Dk?Cp)1mG3V028~!Fi22@OhMi=4)o=I3TJ+1cPX zbRT*9<2g;jjJ7GYwbRdVL2Rs*=%D+E4{K~=b%HArV!d=UG&h-->t}qNR;N-jo*gD! z2eU0co%!jJB&MVRWJg-7b}8E|&Ej`^YI~#MQStx>V7epB=MmF|EMH30>%g|7m?aFsG9UbRiP72=M&&!+B>v3Sj z{d3~|uE6{yEncAc?#_4ffKw03*qg0#BXx$fHcX52YvG>Z(v;YoR?Q((1 zo6hMFF!&=�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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/settings_search-dark.png b/testdata/snapshots/darwin/settings_search-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d078c2c7f9bb6d0cc28821ce560991af8b146446 GIT binary patch literal 7424 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/shortcut_recorder-dark.png b/testdata/snapshots/darwin/shortcut_recorder-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a7592ce57e68560c74bc61ccdbf57b36c1af86c6 GIT binary patch literal 2450 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/shortcut_recorder-light.png b/testdata/snapshots/darwin/shortcut_recorder-light.png new file mode 100644 index 0000000000000000000000000000000000000000..461cac2a19408baf7a62fcd2db2051aae281491c GIT binary patch literal 2442 zcmb_eXHXMb7LJr>g5ZWGRT6rU1d%F83y~&-rt~5RQUoFxzy}f(B7p!RB7*c{L>S@b-nxOEHHj^4#qL-By0iZ&VOVW7@p>{8xWVWeIS%DP z{QshB)Lf;?@YCZx8+>y!an>)^03RZdeZ$Q~rothZmP6BS3=shK5Jsk^?sYCA?J~=G z6~t*#Epcccbd4sr-<%+y&z()fZ(#8NE|Bc0hw5 zu&@N?S$7^$MwWsT5KPeMpNWG_0zzX9+wy^W!_2{ObgY`MQ0w zcnWo6qS_$*iy*;|N8R$7bnaTzI6j8f=#o1FFA=;61}bKZ?1|2ij8#$uf0OQsI^Q+q z7#+rm>3`L#>e!p4af0V0}qdeJyLk#vV`AG<<2|qy~G;U&eh;xV6C-IdSf0KQJ~@T zDt_;%?4vsJt~+Uhy1CYw%%q%U@tW%Q3X%m~WK2e<pa*27midCn_3VO{%Y0 znk`~PcybJSx1OU?Gk!_N2Kun|;@hWRh8P*GhBIbbeH$8UnwqZnW?amdb89K`Y|rp) zubH8`xO%rmFC9f#ec1FD?h6sdFx8;a3qT{F*rTdIbr1bWT0DarK_E;>I`cC>7iyhc zqvzz3q7B0H5oSOMJyc2dv#Yc1+z{K$;Q@D>`VF! z8pxTg3VfQfYB5PBZ!ZRO8btKZZZRP2`HR;Ba5l9T>0B^EHjHn!t0BzH4;vavxHh@G zTxBTA*ArBlC@H3V-TGm1aWnbi1zQT3w>v5R5Ib&1&P6oKd6<8 z$<38=7I1XTX1-!}y-+#x?(7w+mRQR<+>`N@oW2tf0sdERoaMsgMr?ievZrUyv393B zoP8r0gpVIZnO4>FhG6gFVvQJib|VmHN{8V3Q`ca8aZIut0~Bdb$HybWCX>)oFdTn3 z)f-~Md5d5LfJE&g{mtEx4VnPc$`sM9lorH8!R3P#&j>5goy`&5;uf#B`MXn6?5K)T zJz89s5fq$I38*=3_XX?WG0lvd&_h7^ZDC;2wvH*M=-)#HC_6NmJVy))(?G-X(gPPO z_ksZBzTgxUkv6^hF?j_m8hdfEBMu?r*L1V0QVwfSon{|eqqzmz3I7$znom6N6)v?p zVl^lCm9tkF3nkz!+)L&AT}HBCuKz8{SAo18uV)g7-BVN4EU}l#YrGM+m5fzUT7?rg z9cqBJwJ-@U)cCl#hiQ^5BXJP(J?>Jb1duz+VJ)g@)(`(0D%^h{c6@Z~e!@{h7@#~= z&|g&2mgI1w#eX|W0%C%Prj~O3)BeBa0K{p?vG8qsu_5vq3;JwWY~>*-OHokN7bqAR zo@YbS7Zw2j!%C zCD0w|l(t~F#jvR16&zTKT5$&_fpIcWZCkC&5a#v`qW?OJsa`m&{ha7RYO`)9d^oI? z?fyd@Xa+?51w5<)BlKIVMPuWXD{>s4Ouc1ZB?to$H*=sYaMcI*y%UHM3$ zvAYiw{WS}RDeeza!@|SvpfaR7=NOxJQeV10hO#=qN3+Dc>GNFR5}0v literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/theme_selector-dark.png b/testdata/snapshots/darwin/theme_selector-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..792029becced91ea81abfdcfa5febc7d7d8a8065 GIT binary patch literal 6335 zcmdT}cTg0}k_S;lB!jR735tMZSOiwWN)l0Wki6uab6h}j&Ox$E&Pb3P#6?zelAHuY zmYjCU%N}3dyZ`RJ``-O^H`O&g)791e>*<;4s+lNNWqD#kIzk*A9Abqxvg$ZEcL;9B z6$E!~d-*Bma~vE%xPq*drgzTn(x}sdIRnB4tYVrW976+INVQP|@fRs9naCB-liNsF z^hROvT-JMGYhRhZW_A}5u*R3=a!^$C;<705$9b^YFeF&kVf@wf^K7jZm005U&M`i` zH+2>fDtVh)?GW{DpTc!AX#y7Ec}E;P{{IV-*PdL~H9{S2mC9zLiQnXufl=$@EBu0o?@432zk_3VmYN{u}TUJz+t`yp~4JezXSGuF1| zH7^pbcD^%-P)G)l`-M{p={=wE2(hf3l+?XbqWi+Sm5|4&+R3xl5bKG(%I#c|0+jU4 z0*l7VZI-%x(U>@|75zXfJ1FoWfET(k>v#{&;Xfgk(<`tecQo@5;S&2JrvJCXfioa( zzWOU;$wo!9e`))+=*YGpulX2eCDcoZUc>*q zh3}8MPU(~Q8_D9YexIQ(3fhW*Mc(alBK=JmqV1K!TIW}!nAuBNyFV|XSK~YQB_(?* z+AfU3kgG#n-7Hm|R)WuYi$?3MixZ6Mj2iOTa@&Z1!u65fAKIcda7e0lR7Sw~^A)#P z)A;8*g&UlWia&M=fc_4*`{NnWe{Bvp*tiUP)_pl}zB`$4;Iyb^S~S#L+T7gTn7t7b z8OzQWa3J9~A845{EE+TJT3(n+QQYn4{TXvXb2_^V^qg{S*lOh!DLHNvcvhOgoKT32 z(|Jn-$HcWeb#*o^T9*rA)prm^l6AM%$TeoZG&@#X-iMnu8f-@@B;=FsLEh^+3I_&U zbG#^5r^;0sx0<{-t=8H~*sTLuq^EHSRMCKHXxKeCbFiUkIz6k+<3@7z6L z>C<^mj1@m1wt(0e2mjxvaA`O*+m~$C{ExBN6-pi&*v5q>f5aKZBe3;r`|hIo!&*uzCiHU*~epwV5NI5HE7zXp!oSUy1?fMsrAZ#%J=>m<(*)zdfuq{!aqeuB zyhP$tnBZ>Pjcs`R^wCy(5E{f``ux!YHQ`@lxUTXKJWiZlYr0bue&arQ*>?S8n*Cu2 zKf%9Gjnv`I`WNL_0;KSW_U!gKd*Rv}8K;};qt1Z#!SV{*YaxsW)XhVzsy6dp!+iRg zxq4E3eEdL6z0v88o#l~#8%HUEr+nAK{OUSVh1_lNyxubp5 z?~@H5Xb*SZn0}u2Nx!#X8`Bic(8PtWPr&?+T$Ob1dX5{w^O$UJxT^{C)k}Xlf~Q+y z6)gR*_Tr>reR1Ap1Vj|#>bxkXEcatnM{DW;arEUN34&pKewnV3>IDG`Y zhgtVl*GgMo{N^%p!6?(cTYEUQcSUN2)~7Unzfk``M7vq$Yyr}}($u5yA<)Y#RzM$c zoaxV&-*5GIm`}n6uzyiiu(`n^*=I8u)aXBQBg)R!^qBIUph)TvtXWY|Bx4A+;dY7u z3Dh}c=_Fs|Cxb$GQihaR-SCe=>+3lmXFV0U|FRLWEZDzjAQ+K8I>+?4oc}ABpd$Z$ zZrI`|59Ro(tb3%Dq?KC^CFMeV)E+l%_dMOGNl~_zuRuop1eE<31Ysrn`wxnFuuFpE zo$-+*sG&`wcsEni1%bNUXQ_`@jtA4DkZ8Go*!g=)^H0%-=4lfz~)Ok)L1rX24F*_w1yP z{)H@@x1=5JV}cst(aD_~&WXwbJbmXP$oY9z<86Y;YMkSfLUc~vWW%P9Q`dSDyJ1f3OchBC?(I^FF%0pm7t36{ao_He~yz(m@ z7_0dn3=+wG^Q`#3u*NKJZOHc2oz;pFLp%o=vEuuuZrXA!?3y&F^h&nQjcgZ)RnAfl z;MPo_BGz&mOT!hY%cx?JSu3W=PW+i^(hHPfsEojsi1rnIE z#2p@%sjHi%5Y}vvZVaa0JH8$|sJ|;uhDryH$kAJg#Psx*{s>Hu8s+|p<%{v0D_Bs! zmm%kd5UmA`R9s;rspQl&CBpz@c&4w2B5?(n@q2^*#iWMnHV|_gHV{1V*=&MbyZyLB zdGKICt69zr!>unCvYO=L%pnU8-C&GP<6sZnJI8mA49crUL}?uUf_fYOBabOI$;%BQ zGtcKxTSq-QV{pkq{q~f!NZ3L?AQ4|QIK^#}CS!t4@)vMqC$=Gb^sO!ZyzW;QktFN0 z7hQNcvL@|B&*Yz^{Z=guWU`et{UIixdK<$%s(*=JII;&03bV`A(D5hY0%r~LikW9r zFB81OJ`xxqq1`l%>^c1CBf=5;37j~hb3ShGIrpIbdb3zgMy>rVy7P4@DRBRrk<9CX(LiFm?`9l5OnQy#Cr0rE4(N6vRyu8RJjK9uz z?d@)}PaM$&2CDyaQvPP{{%5ro2vIhd3eJu$NY zMZvwYvK^E9=ysP*bnH<<1|P&)UrfwS(An6;$?84*5FONb^6BEVBxxqEl2BU#FFG!> zw30KrzVK`x?!<_?a%%A}9vbC~6~_)_lu{g{iI0%KUdhWZSab6dJ5#S8FlN5>7xQOk zi%YY$wMAO5G+7M(VG6v|_0^u7>*Aqvh)Q^z)W_NULPYWewS2Y=_T9f?D!Q+XsI-~e zuxP!^p4I2Ozzb+qAHllbob>Z4`=iQ*YKG&!Oc{!`u_ffTBsL!6_)y3sQG}F~AITKj z7zTP`V>&S*@TYv6uB~<6ijzVY>BQq*n2`vkHlvzPLICWqS&1`ZC8&oUosX)92Id#u z+4RiJTf7`)Go`&%=v5^bSFy{zm0XaQ^EtYXX8GLvx}~Dr?{qJ~{OprI`Oqfc>ArYP zlwAzod2SR7^Xn%Zk<8Jb?gR(>^>A{$SF@{rSRm~J|I(rvLme~!)~?Rjuwo7GvZ6q; za(aM`wTziJlK!^h>fXUMCzqk1WX{Yyqh0%@?ofV*%{L$dEa=rIgkeZ_+RtSfN7Bk{ zj<7wBBTC4R1Vh<~Sx`YnFjWqQtGOLl_jra&mD9a;JG>Bj5?QeTLSf;3Iubsu$Ex$y0-(2&s3EOPeaIniqggx}f7e({%aH*`7?vh~n zq^)xhWod&ngvlBbeX&1hoaiYL;0#Zj;fxzYMI}PQ=&t2M_+E~|9~UXTr%#u{?^Quw zdfh~us+HSVt-mn3BxT%{P5SMGFg1{GDJp`@b^((E&Vtr^R;oPQJlF`16$iqUR>Gj- zCPUk4G33(pA@47SPfl}I5sS}cWz1~mEs8hYZ_;9qw{0zVTi@6ffpkFxu%W@set}Yv z{l#A(ule%zD8klneXz4CIpAUNOz{#fUNh%7_iF*#x|s-=ab@IgA%H4UWIGz~g_{mF0U zY0KJM>MZUql<}8#H8jMyVlM(tI>@EP^FGLFH)KLmMoS>xs*qKno$YxIlU%_>s7C4D zuCK$TCGp88=4vWDyc#)eprM4JaCWEF40S@?(^|vs%&c@tuTw>_1xR0RUKLIDM|ZlL zRn;!$FHJR$eJF(nOU3?C8cT!L4`gk=JTZKtgNsaMpSC!A4LiQZVhnqyOnVe?!_Cq= zxzQ-}bm-lW9>90&rD;^r;jQgMdO|4yO8i?ty;P0t^ea&f87KR{fmkZEC$dwRHK?Er zDt0Dd#&0&(M2jbY8)RW%Zw}q9e_j^d z#GC_YR z^HAgji0B*4^~mZ1$9X&G5Xre@y2{ zParl>>P4r$1G4m`QKGby&?0jesi|kdw(U{p)ja}O{ryM9#iTz|-b7l4Gb?Dbm)7*L zb@w8YsnEWh%%SrxTkKVempUxkzgKviX*a~7`$Eu~06#olnEDHb;DP9YpPWLLP2GIb zNW@oFRalyq^`Bx^w2Gl2)N~>fP2U^~0Bv1|4KB5LczkYl0EmkXSwv=LZvB!{t~1L(JnQv5qFJR?)M4@{5z`(#1cyogHF;=4H$PSs`&Jj!- z+OhP*+%=ReqetJUnRJCR2*-Xc9&pns7Dnx*-DXEi6xcImfhf zUno92KKSEtFel+X+RUiYR}b1vE|@^nBZgRzwKSsrMS?_#jtp-zKwm5_ksbm3jdn&3 zc(Oz}^YC}`+nSc7%s8?q3KT3T`Jv||@>Y|5FSx09%37OH=XS?xoACpWpyoPMmBTB;yMYEY>3Oa`p9X_eplqGj1ZC8fe+*m|Mq42I`ZQGM zVRG6hi;Lp)^gEy~2t||-aEDqNIgieM_RNBJBxHZFGe0{!g3R@EbKD}m=icQoQU*~~ z&3$`9x8=g6VFd8cXsJpP#+M^BMdz2^ulI??ZiDf26zld`{$4QgAm1DQ1ioNsjx?Eb zIFdH9d{|rrF?W0d`|Q1P^Rz2pNd?SQt68oi8go9F-9z)1gK9kR=Fhr9S!ByyTp-bS zq!DcgASo`B#KA|B4Pa8@xzA&x5zDWU0g%;! zshz3Xhp4dE5BeWs{7p?$kWd1{mXGA*kM<<&wpCy&6`B=5t5Ix+7$FN)2D9_yRq2Z9IZwv+Y6CwMd5aDHy!&UDsSB~-750-q8^Z_2&5!OW>crq86vIMcRUsMKa3NopAt4c=H#q?p^S4zul{1)6)bH1f;r3KFjb7ms0`#5W z&u`pOI=deQ9Cy0(aQUt2h)|g<GZ6u*x?9ydLWhe$P&xYpq5_n4^h;~zO zLIaT&}33v34t>Q%F2dO4i#N(&dclBfF$40s*zT^*@8m z5w%wd{1F}EfbcpU?pK&$H7C=n8vD68vg3tgJmA>dRix{DqlrX%kK09D-XH-iGUy8d zObYkiN8Gp^0ipLEUs^6N9zM84e_3dH(GpYMe|u4nR=nyKWA2wvu!UT9g|tQmg^IAl zrrvVbJm=-bpePe=%d*y8SZSrcj!sUZch($sK$YqqXGbexR(_jR?W-Vb^ZCYKP%vd{ zfv}{Ms-~vv?wP11(ea3-*4Mt?BH-z16F0-?AfKWnRQXAofEWu|WJzh7af=rp*nNl5 z^5JoOL;**DD<>HA=w%V?ad7bE!nbTJFYME8Yiz~A55hIZ0sJ!tD>k;i&UwP&P{r0n z+IB7JPJ64IMTyTKIax6Uo~+O%M0{rJGJln^62VK!dNBZ1)l=vB*&9eK)2ZHX*^tQ- zDI8V^FW2TYYNcN7+woBhph z22znIl>r;v=GA^z1PG_41@PmMIYcLYzzsKRSEwvgmvS5BpIEAvno3y=^tV}At&S27MBQ;!uuq)_G=5+ zL905&U^2lq+AM&_gblop?%QstwLw|^s(U=szp265eU?$2T*-p9f^aAFq}J_07ycphyVZp literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/theme_selector-light.png b/testdata/snapshots/darwin/theme_selector-light.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce1ef9e33b41f53e8263b12b78b6f6a3b80ad48 GIT binary patch literal 6221 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 Tncyap%ciBKuliZ}S@?eeLD+-e literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/two_factor_input-dark.png b/testdata/snapshots/darwin/two_factor_input-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..fb1b1b8070e2d278dae8c163f87a56d8843dd540 GIT binary patch literal 5082 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/two_factor_input-light.png b/testdata/snapshots/darwin/two_factor_input-light.png new file mode 100644 index 0000000000000000000000000000000000000000..95eab1701943002968593b7964898014cdbd14b0 GIT binary patch literal 5009 zcmeHLcTm$!x5k&(7eo=IDP5E>^bShq{R?#%Aa?#|BH*>lcw&a)?4U-vO111AFw4Gp8V z7TA!6=ECRm`6v26&d2`l_iZ#ZET-DvM-cyv^=X7Ya-WB8YX}LxFd%iSvMljoYR=?y zIt_6VW8Ztw3pTHF(56v5y-C2kXt)Nx%x|x_syYjugH-}wViRN6kI_5{vu>L*``AEW z5Q7R!x7Nvgq!R+Yg`t;xDS_g8*rr+7nU?BMSL4uELhL4z*mGxf1o4di^)0m>`pL`I zbnCBpDrkYWtCJT%|7QyYi^=H8*vA7Mmx-N~hW8<>UZp=96uINwGS3vDMu!!!dyehysRK$}x^_`>vNc`q z^GmflVD7Gqk?(3#pOwDC-Q#i$+H!C6yjkHxji-*#wYjGE3?^(icX?n~!h6elW2~Da zdqEhbh6o$?k8hvzCuYtU7FG82tf=Yry`z7RYY9mqkmC`ZTU)v+O_bx`eMu`^UaI_| z5OR6Z{`jc(JrBsh#2jJ%V$x_&+HFok5D&EM&n$QGav{@???!`5$9c=q=);CwOtCAU z+nmf_;a(e4weh@4@>*9w-+X5L7d9B4IlQb8104&-FI?q5DD__uip6~?zK5!&0a+6R zCX-S(VUi{K zTwJi04Zcd^?zh+Gh1p}5B8f)NgeXk`+T2~wO(rVts1UJbWQJ#XP@d8vk=@-S7K7!S zo&HE2_@<PNKU#V?u}o%6ETc3nHC@10G%d(Z-3S|-yQ5fO#a-S&%zE8!s_A)2|O zw{EqGTAm4Vg<1C|7DQ{!hNS!bVo%?Iad&wTj=pxz?-~{*CLQYO#KylJ9W~4ty`Eqp zi9%&f;qm4s=0gn?nD)Uz3IHf6;np3_R#zKBSI1oP43$(9QFP5$H3gUlKFXBjFYDVa zMAy|3-;$OG5ZH07PTmJns!M5XtqALB)Ed*(*Ym1++)FB3gdiiz{+&p{U(?TRNq^ zb>sT6_VHUk z$)Q0H=V5ezNPTv8{!;(1XX)~!-8V^hVIVkJs`N4kXSROD@em&6KmUu03Qo(&$oi0B z@Z5}gE~$~g%gXvTlLr8@S}-gO7uB;TBBHZdgx+vo>1=jLD=uDr{n`ZQ#p~Trl$V?9 z^Yz=|;b8#n#XuX+98+g6@0B(;8R1Y0Hu{(SY4qorb8hf8TxW~W$EOna27l;n(k zrKxFLysUGyh$~d!?iSL;fJz$-RFLs7qKMR)y>(4d>8mRPW#$A?Q4XDymg$KJ1yh{W z7oF@al=Pi6KO3j0%*?2bb!>!t$vF>`b(&!0fsV0fj^ zDTPAWrBHZ+*Nk;=e0zTdR2>`~fK^yjt72}EZ-gDkzln^DlncK6uTvlK{`%>_Oy*vd z-;xcD+Wu6_%*GlctyP=jC7n-ys*l>I$$J#!>W;f=uEvfy%fdXZfT@*!NK(URjUHK# zu!ss4_VP(<_bW5?HJkVE?@6#e)hcEm`$$wuutoWB^YJec&y@C|pT7L<5Y>A%iAM|s z0SedkaoPXn2K7AzV2IpiM)(y8Xfs4IyR}bM)HszhPExN-RvNp>R`IJf4r_g2Wkh3) z)2G9#Z9GiPDb@T1ly9gj?YRo%kZl`zj8x3)Y4{@pk;Kw*c@^dO--qy+i8nxlK|SPQ z=sOuu25zqZsuQ4yK@R;pn8N>np0r*AZcw>8>Mi(rVtjCa@Pq9}%|Vt>tQNgCon6GW z^jQhLb3ZxfJ&iD7Ks|{mR0cWq)=<{tr71msQJk}V;K;noM&Ry|=S!AT|_v+RFD}D-yYv5uZj!RzrPk^xK^E2`E_#riBL^@L+18FN$>DTT_Ig_1W?%5Tzfq8 zuq~&ms!rapwUn-~-&{2%!cv*Fz_}r>)`+awJBgZ2;8QiZnTaJ;T~~iv29)9I4pykP z%DJ_bbRR8tM!W$~k@TuL`T)vwSul+FHw`SMX`3KI}cIsVTpi^Fgq^}Ht~ ztSi>*gwEGON7aK;k56}b zX2i(k5=Q0O4Yy?=N~JR4yLVea2XUQpU0ua4{G-1&(Pem=eC?u*LPgP7A)-yPF*D|= z#7-;qiErSeP`L!~2uRhLQcus19R!*QAM`eQ!s%bj4r#21)VY5pRmlym3_7n^i}S=} zl7fNl9$5_o*W4^A6Jyn&?j;7Eg}?ublG{Rc?YcE46h$_7CvPslBo53?17bTH;{1!H zJ>#^J^}KW59r3^TGsn3@svweyt5m&Ke^XIj-xd{jPxzmFgWa15*6d!NL)#70xPN$~ zZ)RECQ}UQu3;#n3oWHze<}DE2$F^gPImsZEEr&nNx|TCq*mZW4l^A-VFcdSvkPzU=VrBvtf=|1**`Huc8uDw?fiQ0mTI0Bc`yI9ShV-n;n0 z?YjEs;M|JoO9q0@~2@ zRKvnV50jup)!)A&!jcVK(&mH65`0v6H6>r7F{xj8lsN%xRRIqOgtb1R7bQE2q+QjM zcNLK|DZJqzj{@xw){#uD6@hs7P&hR-1tY|w?kn#@O@)0eta7w#ZUs7!a&n*VMEKQa z#adG1T))+r3ThX#KIy6~fmO2kKO*0@*EA}t8+JD+*6>j~^^xJy=ocF2Sq z4GdR)yDl%fF9;CGQr~E>DFDj*n1MgUp@^>e_#1hS+cM29=&pqM3x? zrZF#{Nq>KYd&qv8#pTEO>=0=6S&~7(kx$3SM%+M|WuNH}J-PPJ~kqqe9mKNpYR$ChoueJFegL2c$WU~jrCb7{9pz9kfK)HADA zAoApeT@X2*FO+{`*mv9PSs!PRRVznU#$?`Y)TSo*5TIqOz<<`+l|EROP1 zYPO^Oby(fKlQZ>ctw9E>$nv=mI47=CUu8f^_-Fk0RE^?v~HQxCHM literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/usage_quota-dark.png b/testdata/snapshots/darwin/usage_quota-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..70396b1c3a13f83e5b3ed39783a0507e13395b57 GIT binary patch literal 14269 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/usage_quota-light.png b/testdata/snapshots/darwin/usage_quota-light.png new file mode 100644 index 0000000000000000000000000000000000000000..72d3962740730fa9e68307aea2c1782554c92e41 GIT binary patch literal 13780 zcmeHubx_>FwkAn%NYLQ!4grEoaCi5hgS*=#xVvj`cZUE00)xA|``|X{n|tr>*8aJ# z_P(lpRXaV^)!jAqo9WZ%obNm5^n@!bN_|8oK!$;V`6weTt_lP5t`$1oM|=GTj5 zU|_yx%7}}oduE+x`>10rwEyv-L3#&|M2_?q;J1)c{^MJG`JkG$PoyErB85a#pp8_;4nuvtt3h35# z&JX^%z|qr}F|#}8%bm>523)41XPSTf2wSnu59riknBqwJuqpv`B1vSj|J|mf0F~og z@4%iP6Y|EK1h;qvQx6~(-#c1fzsFRAj{EfU=dqSDT4$wglK-(Y-)C{JyCXn&k!K@Z?wg^S_RxW&@SioRRd&_E-akR(dCs+ev#zm6YCGk0wMTRRd8 z=}j7GKV|^g>~S#%U_3M!e%nHgNr&iQpQf}de(&Jzc`L$grNs{ieKV5(9gOEJ9%@LH zjNYEd9^)%87-a&EyG2D?In9eFkqnX2E-F0wGHr8eIjWw$p2eV5eC8%>igMd9(zIa- zG4E>nYBWl|Np{R-PXZ)~Mks{3QOua&L8AuGz((bGSew^6hRvyvJ0{F1$nx|Bw8%x1dEmgvWx(O_7;2@5x}*!=w2lUvoJvM~G}(L6GKy@1RWM6_HI z;?IyX{t+%)Z7WWKj_RiyZ@=k%VwZdzICzP!uG5!@2w9gk+W6fGKR2sCNw^jhI;{W) zWW|>8_=4@jw1*$`tl|>}sOB_D>B<(mN$HSjU#>`nmVlLIOMClq>5;T<&(~#O<;QG{ zjQAYmar!QOeb2g!s{8dv%P}eIB0g^Z1>4d-tI!fSB{^yNi?q%7R|9X4{TaH5^0M;q z99+REpZwr154$xD$F3g-!vz>?)#s+6F-t2e!1}F3I60hM;)xCoFO+-NzaFOe( z3>xO<3&PrUiZu%$aVcjWcOyHcao(KJn%dp%{(Lv2+>Tbq4ejQzezR)*rpx6f!d5Jp zM}FkCZTZ)9ov8*#G1`o;V?A3v1)vy7#AqMytYVp&)yEHds_NTsS>2Nc(tPnf908G$ z5g4M}4y&$?j)-Xlf6Um$C`IQO-0a+#yVMBby0LvPmvE6PFjenMnj0G}IWzv=hI1;= zp3A?|-_xR~M8HR+0Bf^A2U}d?D(X5`Iu9sYM4tc;gIg2wzQs4PWx?)MG>hI`6(79B z^G9*yh=_^bzxVCyEha>T52qr%Up<%ym)jYEB@n%QVnB8&vvXg=Tng1n9?n-^A+!}6 zD{&W>lX-y=a>LRI&jL<%S2@fh8;0%X*YGndJ$snG@zh0HHRM726S3*IPggPE@XSQa zEO?$RMVQ0$u-I_F7e5IK4Ew_%#*MF>6pgp&$@=t8xNZD=d=x?0$iW?hYqJq?mcOKn z)Q?>Gg7vrd&j!A;p^Msbt!QlOmX!8+uuJqid``ztS9@Rbw;%L%misyGF|f1KPv4hF zly3f*TW?K9R8$o`g;K%RkUJ`B8bLIDJsGn>0a9z++KE*DfGhYa)gVA zhmME$o#iDXBjeLcoQ{S@g=(2X_q}au(Zr)y!_w*ATg&hQ=z0l2JLW4iyJir^|L&KO zOT+d)cz8vnMsf>2bG7`p8^if*ZxPCh|0tnGA(j=N=wLi&nOc#0nTGXs9j|nPIs<>F zLP~0i55yDlm^n{n(J5d9N+IS{$RuWV#rot(@&n5*U$uPE{q)XCJb_9>R>K3+Rxoq! z=LMW^)}l@RX!7of>mv$W>VY`3wcWRVYV*0T^?73O>5AwPX&AjbPP|0pK}bqZ5HFY0 z(~F{%!^Yyz*LPo9Ld6rhw|8y@NIa≷Q_Ja_fQmO!bFf!T3`3ton4TS9@nr7Yl6g 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 OFftN~;?<(x1O5YFLtoDT literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/user_menu-dark.png b/testdata/snapshots/darwin/user_menu-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f4426d658db7e559acbcc2bae10d3a372bec7d18 GIT binary patch literal 2892 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/user_menu-light.png b/testdata/snapshots/darwin/user_menu-light.png new file mode 100644 index 0000000000000000000000000000000000000000..1f1644ee766b7b3e63c7db3a494ae4b12fbda3be GIT binary patch literal 2813 zcmcIm`#Teg8&^~k<+xjNC(ErgL?W_ggp3h$%Vo%R<~C9xF}E1g+{Fop%%wI=GnaA= zwH=q(+!m@82ZhkwzRvj%zCWBF-uLr9&-;0v_kEuC^FGff^`;#}Lfn4o1m+VE1vukda7CyI15H&Qf>&W?d-iY<_Q6 z(Ho+LjoJ+`Tt~7|tWF57shUL^wto-^ufAgjg5lh5201thIRz<;p@2%>6uS^1{F%vA zT~*H)hV&nv0g+tijPX7t#zg*fXzxqr{;eT~(Zw+)W2Lwt2HC3dcgNMMibX()*h8>K zL`5~LQ`89^;}m2rub;(iV8r*Bwb zgw5}D8_46kJC%S+Hzfv@<&{p@Hb~$EGj8g;I%HFdv;52*o}}ZNjn-=Ij+sVu@uP|g z(AO%6-IkYRZlr}CWnUVL&pMJ>k*amJ+s`uFS1SN3C8amzc9|cc^HNsV-$d^CksVO~ zclSdHWAr2&ibjfyr0sy_IrgjSQz$PXgM>fRJw?FMkAx``Tgp?LN5sSrXUjEnr6*3T zhA6!JELTLpM9j@vMJNT{{@cXm;dc8i)B}PdYzU_acI;)`_udvG%Q}l?cUx+F49L>= z<&BQdK{)`SEwu~espBZ49%Mdo);mkR;GOf9B>4vz)SsAOK6MC1>p`Xye}t$4dmD=F zOVK7SSrYRK3VA9l|fTO;W-(<$GB^+_*r2o@A-gF9%-lv;PqE| zg^GfBtkrQPV9Gx&+(E17R(ct$sis=ivV-4}?4y-*`JuK8OIYK$nm|0*WTr*OLgT># z2D7DmBi!!3IqoqzEF&uNWNTQK1qkdA2D8vAKSv^I&`Tb4JGiB1B7_pQ5*0scdk;z) z@H<;W#axPD5Kms$+o5^R&Ijho_GhNg8{D@y`@PZ@dq4U;>|>eEx6X&obv@EvX7t@} z9%EkU_^thc%+{Sr)@UtLif?Del6s;5a`Wxbd-!8lK^tEh+TVd5P=H7M>N6M+C zXahUFKc_2GGWa57_DU{noQ?PLluom#hD<-yu3`z_0{+w$>3C&&MR%w1SIP9-PS4$l zjf9TYy)P$pX9>0KbxIromVtKBAsEc>-X_0e9-|%W$7!&?@{<=&FNrO_9{78N&+Mw4 z{Ql0@;Pa`@3dg7~)DPIhDGo%{nHDKH;YwWGNL(numGxqp<8cdaelcC2Lj&Cgp?`5c zlxaW#7w(=oIFNSDuf}!t(pf*FH+s|@Gnp=y(CVpOUQ|;vCi94NS$s{D*(NBsK}_IB zf85NOMKh>qcV9A|ZIkvpn}a*mqC+Y6&aYk8;G4`Nyno0AcDWq3P%Y#>e|=^g3@4_T zT@5TOZ_FB? z4Jj6l%7Z9j2as)cVP*AQUGMFM2HCYwwEbZ<7&tOGlsAh7B?V~PX?9grX8ar9HqiSJ zm?}Y0Z+jQeW1z2he!<8@Ur+SAofLhMR1|4|T_2qqrpdA%R3#zt@|)3&q$(YwV9mh^ z1gV8S5IIGaW%WWJ$w%!oOU+i+7&$cej7ciBu^ElNH2wUBY)5!WUj3|In%wMbo{b3 zlPzFQB<3!6G;=rVJ!;59OtVel*r#xrqztf>6C5eKnD(s6mmFR^VtmP9r_ay}al2wq z=bZf0Lb^M*ehLPw58}ndnuM*fDz6{qo!mpRt+G|~--_Ptz}|R{E4{_pBXSMJ4`0F-<96M>>8Vknse5lb5l}L7a^IPKtB=~i44G(oF||Izefzb zJ*Kc+hYnPPQtHXGlWnK4OVjhEr3wI(HbBvZabh(RYwQHh&=G*!dh9Di{#DBexrdU8 zs({`hmJxs`M-_e`6~EjEz)aeRTKX=UK2klK{1dh~`Zige^2;PZx;X{HXitFTuDT6Q zn=-RfQS)4;xWEv#JYnw!TTrPn2GW<>zBd}^Mtv7L((9Q88FJG2=u*~?-J3PWMa%`2 zO8+}c73j!1JkvkwN`K~WD6a%?i>%VgK0NbSor0^7e3~kq|2xn1Dk|tGbXIX*#qu62NE(0eO6CC zl=&4MX0h*|OLMOw`|Bq=2MY%P!|1DNHu}~l`;gZNgP}_KM4z;CL5Y>$q?=)|v>u$d zMwn|l?kBjNEUWNTz1jgzaGCa1VITaGj=c3()YY~f z`Ipv6qx3R{#W(IZ=orbX}xs{hy%Z^Lv z>F7J_^Y$Zcb5>hDrvc% z7JrdY06HrRfrH^ud8sJvB{Y|rQT%oBtf0>}9zVh3^{1Xth&Jil=GhEbBPJ%CeJ|G9 z6@;ol)5xsA?$+Jdea)5Drq02E;Z+D-dXY#`Rnx0&_GcPI?0h_yd{V$=3>a*@vi;1n z3Y4=HzCf&`h!o#lVa=-SNn!^m!@E}=V+Y_Y;TlKe(E8`nif@%9o^g~K(=9PkCJh`s zpt;dBv$wl^Y@>163GNC_E0CNNz8(HyC$2yD`dH`Lo>!tC8j<6i;ZO|Mu;k92j=l(L z*_XOsBKj%ZPp`5B!NpiOd^*a&YzRS%%ln4P(bumRG909&S}Frl4^COPgxl3>S%d)B90HVC$IJEQt)6G^7TAp!#PJiS(AF45UOnVVb^1 y>P)I8XPx$YA+W%<*qZ5exr}@S-t!f0gvw%LfJ9S=L*4Kl&fG08xYh literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/whats_new_dialog-dark.png b/testdata/snapshots/darwin/whats_new_dialog-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f92cec4ea8c4d1177ad0b97055341ad027dce5 GIT binary patch literal 25019 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 C=z>xJ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/workspace_switcher-dark.png b/testdata/snapshots/darwin/workspace_switcher-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ecb14253de3e1a0c2fb69e0a0d1253f03745ba80 GIT binary patch literal 4538 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 G|Nalpfof0y literal 0 HcmV?d00001 diff --git a/theme_selector.go b/theme_selector.go new file mode 100644 index 0000000..62d537d --- /dev/null +++ b/theme_selector.go @@ -0,0 +1,88 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// ThemeSelectorOptions configure a ThemeSelector. +type ThemeSelectorOptions struct { + Disabled bool +} + +// ThemeSelector selects an appearance using preview cards. +func ThemeSelector(c *ui.Context, mode *Mode, opts ThemeSelectorOptions) ChoiceResult { + if *mode != System && *mode != Light && *mode != Dark { + panic(fmt.Sprintf("mujica: ThemeSelector mode %d is not System, Light or Dark", *mode)) + } + k := Tokens(c) + d := Density(c) + var r ChoiceResult + r.Element = ui.Row(c).Gap(d.Unit() * 3).Wrap().Role(ui.RoleRadioGroup).Label(msg(c, "theme_selector.label")). + FocusGroup(ui.Horizontal).Disabled(opts.Disabled) + selected := mode + if r.Element.IsDisabled() { + copy := *mode + selected = © + } + r.Element.Children(func() { + for _, m := range []Mode{System, Light, Dark} { + card := ui.RadioBase(c, selected, m).Column().Gap(d.Unit() * 2).Padding(d.Unit() * 2).Radius(theme.CardRadius).AlignItems(ui.Stretch).Transition(colorFade) + if card.Changed() && !card.IsDisabled() { + r.changed = true + slog.Debug("mujica: theme_selector chose", "mode", m.String()) + } + on := *mode == m + internal.Ring(card, k.Focus, theme.CardRadius) + border, width := k.Border, theme.BorderWidth + switch { + case on: + border, width = k.AccentText, 2 + card.Background(k.Selection) + case card.Hovered(): + card.Background(k.SurfaceHover) + } + card.Border(width, border) + card.Children(func() { + ui.Box(c).Size(132, 84).Role(ui.RoleNone).Draw(func(p *ui.Painter, rect ui.Rect) { + switch m { + case Light: + themePreview(p, rect, theme.Light()) + case Dark: + themePreview(p, rect, theme.Dark()) + default: + half := ui.Rect{X: rect.X, Y: rect.Y, W: rect.W / 2, H: rect.H} + p.Clip(half, 0, func() { themePreview(p, rect, theme.Light()) }) + p.Clip(ui.Rect{X: half.X + half.W, Y: rect.Y, W: rect.W / 2, H: rect.H}, 0, func() { themePreview(p, rect, theme.Dark()) }) + } + }) + ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).Children(func() { + if on { + ui.Icon(c, icons.Must("check")).Size(14, 14).TextColor(k.AccentText) + } + ui.Text(c, msg(c, "theme_selector."+m.String())).SingleLine() + }) + }) + } + }) + return r +} + +// themePreview paints a miniature themed window. +func themePreview(p *ui.Painter, r ui.Rect, t theme.Tokens) { + p.Fill(r, t.Background, theme.ControlRadius) + p.Stroke(r, t.Border, theme.ControlRadius, 1) + p.Fill(ui.Rect{X: r.X + 1, Y: r.Y + 1, W: r.W - 2, H: 12}, t.Surface, theme.ControlRadius) + p.Fill(ui.Rect{X: r.X + 1, Y: r.Y + 13, W: r.W * 0.28, H: r.H - 14}, t.Surface, 0) + x := r.X + r.W*0.36 + for i, w := range []float32{0.5, 0.38, 0.44} { + p.Fill(ui.Rect{X: x, Y: r.Y + 22 + float32(i)*10, W: r.W * w, H: 4}, t.TextMuted, 2) + } + p.Fill(ui.Rect{X: x, Y: r.Y + r.H - 20, W: 30, H: 12}, t.Accent, 2) +} diff --git a/theme_selector_test.go b/theme_selector_test.go new file mode 100644 index 0000000..5906fad --- /dev/null +++ b/theme_selector_test.go @@ -0,0 +1,48 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestThemeSelectorPicksByPointerAndKeys(t *testing.T) { + mode, n := System, 0 + disabled := false + tt := accountTester(600, 220, func(c *ui.Context) { + if ThemeSelector(c, &mode, ThemeSelectorOptions{Disabled: disabled}).Changed() { + n++ + } + }) + if err := tt.Click("Dark"); err != nil { + t.Fatal(err) + } + if mode != Dark || n != 1 { + t.Fatalf("click: %v %d", mode, n) + } + tt.Key(0, ui.KeyLeft) + tt.Key(0, ui.KeySpace) + if mode != Light || n != 2 { + t.Fatalf("keys: %v %d", mode, n) + } + disabled = true + tt.Frame() + _ = tt.Click("System") + if mode != Light { + t.Fatal("disabled selector changed") + } + defer func() { + if recover() == nil { + t.Fatal("unknown mode accepted") + } + }() + bad := Mode(9) + accountTester(100, 100, func(c *ui.Context) { ThemeSelector(c, &bad, ThemeSelectorOptions{}) }) +} + +func TestThemeSelectorSnapshot(t *testing.T) { + snapshot(t, "theme_selector", 520, 170, func(c *ui.Context) { + m := ui.Local(c.Root(), "m", func() Mode { return Dark }) + ui.Column(c).Padding(16).Children(func() { ThemeSelector(c, m, ThemeSelectorOptions{}) }) + }) +} diff --git a/two_factor_input.go b/two_factor_input.go new file mode 100644 index 0000000..3e3282d --- /dev/null +++ b/two_factor_input.go @@ -0,0 +1,75 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// TwoFactorInputOptions configure a TwoFactorInput. +type TwoFactorInputOptions struct { + // ResendAt sets when resending becomes available. + ResendAt time.Time + // Error displays the caller-provided verification failure. + Error string + Disabled bool +} + +// TwoFactorResult reports completion and resend events. +type TwoFactorResult struct { + Element *ui.Element + completed, resend bool +} + +// Completed reports a transition to six filled digits. +func (r TwoFactorResult) Completed() bool { return r.completed } + +// Resend reports a request for a new code. +func (r TwoFactorResult) Resend() bool { return r.resend } + +// TwoFactorInput collects six digits and displays a resend countdown. +func TwoFactorInput(c *ui.Context, code *string, opts TwoFactorInputOptions) TwoFactorResult { + if code == nil || len(*code) > 6 || strings.Trim(*code, "0123456789") != "" { + panic("mujica: TwoFactorInput needs up to six digits") + } + k := Tokens(c) + d := Density(c) + var r TwoFactorResult + r.Element = ui.Column(c).Gap(d.Unit() * 3).Disabled(opts.Disabled) + disabled := r.Element.IsDisabled() + r.Element.Children(func() { + pin := PinInput(c, code, PinInputOptions{Length: 6, Label: msg(c, "two_factor_input.label"), Disabled: disabled, Invalid: opts.Error != ""}) + r.completed = pin.Completed() && !disabled + if opts.Error != "" { + ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).TextColor(k.Danger).Role(ui.RoleStatus).Children(func() { + ui.Icon(c, icons.Must("circle-alert")).Size(14, 14) + ui.Text(c, opts.Error).FontSize(FontSize(c, theme.CaptionSize)) + }) + } + wait := opts.ResendAt.Sub(c.Now()) + label := msg(c, "two_factor_input.resend") + if wait > 0 { + secs := int((wait + time.Second - 1) / time.Second) + label = fmt.Sprintf(msg(c, "two_factor_input.resend_in"), secs/60, secs%60) + // Wake when the shown second changes. + c.After(wait - time.Duration(secs-1)*time.Second) + } + ui.Row(c).Children(func() { + b := Button(c, label, ButtonOptions{Variant: Ghost, Icon: icons.Must("refresh-cw"), Disabled: disabled || wait > 0}) + if b.Clicked() && !disabled && wait <= 0 { + r.resend = true + slog.Debug("mujica: two_factor_input resend") + } + }) + }) + if r.completed { + slog.Debug("mujica: two_factor_input completed") + } + return r +} diff --git a/two_factor_input_test.go b/two_factor_input_test.go new file mode 100644 index 0000000..5bf5aeb --- /dev/null +++ b/two_factor_input_test.go @@ -0,0 +1,96 @@ +package mujica + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func TestTwoFactorInputPasteCompleteAndResend(t *testing.T) { + logs := accountLogs(t) + code, done, resent := "", 0, 0 + opts := TwoFactorInputOptions{ResendAt: time.Now().Add(-time.Second)} + tt := accountTester(420, 220, func(c *ui.Context) { + r := TwoFactorInput(c, &code, opts) + if r.Completed() { + done++ + } + if r.Resend() { + resent++ + } + }) + tt.Key(0, ui.KeyTab) + tt.SetClipboard("381 942") + tt.Command("paste") + if code != "381942" || done != 1 { + t.Fatalf("paste: %d digits, %d completions", len(code), done) + } + if strings.Contains(logs.String(), "381942") { + t.Fatal("the code reached the log") + } + if err := tt.Click("Resend code"); err != nil { + t.Fatal(err) + } + if resent != 1 { + t.Fatal("resend not reported") + } + opts.ResendAt = time.Now().Add(65 * time.Second) + opts.Error = "That code has expired." + tt.Frame() + if !tt.HasText("Resend in 1:05") && !tt.HasText("Resend in 1:04") || !tt.HasText("That code has expired.") { + t.Fatalf("countdown: %q", tt.Texts()) + } + _ = tt.Click("Resend in 1:05") + if resent != 1 { + t.Fatal("resent during countdown") + } +} + +func TestTwoFactorInputDisabled(t *testing.T) { + code := "12" + tt := accountTester(420, 220, func(c *ui.Context) { + TwoFactorInput(c, &code, TwoFactorInputOptions{Disabled: true}) + }) + tt.Key(0, ui.KeyTab) + tt.Type("3456") + if code != "12" { + t.Fatal("disabled input changed") + } +} + +func TestTwoFactorInputSnapshot(t *testing.T) { + snapshot(t, "two_factor_input", 420, 160, func(c *ui.Context) { + code := ui.Local(c.Root(), "code", func() string { return "3819" }) + ui.Column(c).Padding(16).Children(func() { + TwoFactorInput(c, code, TwoFactorInputOptions{ResendAt: c.Now().Add(42 * time.Second)}) + }) + }) +} + +func TestTwoFactorInputCountdownRoundsUp(t *testing.T) { + tt := accountTester(420, 220, func(c *ui.Context) { + code := "" + TwoFactorInput(c, &code, TwoFactorInputOptions{ResendAt: c.Now().Add(100 * time.Millisecond)}) + }) + if !tt.HasText("Resend in 0:01") { + t.Fatal("positive wait must show one remaining second") + } +} + +func TestTwoFactorInputInvalidValueKeepsSecretOutOfPanic(t *testing.T) { + const secret = "1234567" + defer func() { + v := recover() + if v == nil { + t.Fatal("oversized code accepted") + } + if strings.Contains(fmt.Sprint(v), secret) { + t.Fatal("code appears in panic") + } + }() + code := secret + accountTester(420, 220, func(c *ui.Context) { TwoFactorInput(c, &code, TwoFactorInputOptions{}) }) +} diff --git a/usage_quota.go b/usage_quota.go new file mode 100644 index 0000000..64d33ae --- /dev/null +++ b/usage_quota.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "fmt" + "math" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// QuotaItem is one metered resource: Used of Limit, in Unit. +type QuotaItem struct { + Label string + Used, Limit float64 + Unit string +} + +// UsageQuotaOptions configure a UsageQuota. +type UsageQuotaOptions struct { + // ResetAt provides the reset timestamp; zero hides it. + ResetAt time.Time + // Warn sets the warning threshold; zero means 0.8. + Warn float64 +} + +// UsageQuota displays usage bars and reset time. +func UsageQuota(c *ui.Context, items []QuotaItem, opts UsageQuotaOptions) *ui.Element { + warn := opts.Warn + if warn == 0 { + warn = 0.8 + } + if warn < 0 || warn > 1 || math.IsNaN(warn) { + panic("mujica: UsageQuota Warn must be between 0 and 1") + } + k := Tokens(c) + d := Density(c) + e := ui.Column(c).Gap(d.Unit() * 4).MinWidth(320) + e.Children(func() { + for i, it := range items { + if it.Label == "" || !(it.Limit > 0) || it.Used < 0 || math.IsNaN(it.Used) || math.IsInf(it.Used, 0) || math.IsInf(it.Limit, 0) { + panic(fmt.Sprintf("mujica: QuotaItem %q needs a Label, a positive Limit and Used of zero or more", it.Label)) + } + share := it.Used / it.Limit + col, icon, note := k.AccentText, "", "" + switch { + case share >= 1: + col, icon, note = k.Danger, "circle-x", msg(c, "usage_quota.over") + case share >= warn: + col, icon, note = k.Warning, "triangle-alert", msg(c, "usage_quota.near") + } + amount := fmt.Sprintf(msg(c, "usage_quota.amount"), statisticFormat(it.Used, 0), statisticFormat(it.Limit, 0), it.Unit) + ui.Column(c).Key(i).Gap(d.Unit()).Role(ui.RoleMeter).Label(it.Label). + Description(fmt.Sprintf("%s, %s%% %s", amount, statisticFormat(share*100, 0), note)).Children(func() { + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() { + ui.Text(c, it.Label).FontWeight(500).Grow(1) + if icon != "" { + ui.Row(c).Gap(d.Unit() / 2).AlignItems(ui.Center).TextColor(col).Children(func() { + ui.Icon(c, icons.Must(icon)).Size(12, 12) + ui.Text(c, note).FontSize(FontSize(c, theme.CaptionSize)).FontWeight(600) + }) + } + ui.Text(c, amount).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)).FontFeatures("tnum") + }) + ui.Box(c).Height(6).FillWidth().Draw(func(p *ui.Painter, r ui.Rect) { + p.Fill(r, k.SurfaceHover, theme.ControlRadius) + p.Fill(ui.Rect{X: r.X, Y: r.Y, W: r.W * float32(math.Min(1, share)), H: r.H}, col, theme.ControlRadius) + }) + }) + } + if !opts.ResetAt.IsZero() { + ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).TextColor(k.TextMuted).Children(func() { + ui.Icon(c, icons.Must("clock")).Size(12, 12) + ui.Text(c, usageQuotaReset(c, opts.ResetAt)).FontSize(FontSize(c, theme.CaptionSize)) + }) + } + }) + return e +} + +// usageQuotaReset formats hours or the reset date. +func usageQuotaReset(c *ui.Context, at time.Time) string { + left := at.Sub(c.Now()) + switch { + case left <= 0: + return msg(c, "usage_quota.reset_due") + case left < 24*time.Hour: + return fmt.Sprintf(msg(c, "usage_quota.reset_hours"), int(math.Ceil(left.Hours()))) + } + return fmt.Sprintf(msg(c, "usage_quota.reset_on"), at.Format("2006-01-02")) +} diff --git a/usage_quota_test.go b/usage_quota_test.go new file mode 100644 index 0000000..503d2ff --- /dev/null +++ b/usage_quota_test.go @@ -0,0 +1,60 @@ +package mujica + +import ( + "fmt" + "math" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +var quotaItems = []QuotaItem{{Label: "API calls", Used: 4200, Limit: 10000}, {Label: "Storage", Used: 9, Limit: 10, Unit: "GB"}, {Label: "Seats", Used: 12, Limit: 10}} + +func TestUsageQuotaLevelsAndReset(t *testing.T) { + reset := time.Duration(5 * time.Hour) + tt := accountTester(500, 400, func(c *ui.Context) { + UsageQuota(c, quotaItems, UsageQuotaOptions{ResetAt: c.Now().Add(reset)}) + }) + for _, want := range []string{"4,200 of 10,000 ", "9 of 10 GB", "Near limit", "Limit reached", "Resets in 5 h"} { + if !tt.HasText(want) { + t.Fatalf("missing %q in %q", want, tt.Texts()) + } + } + reset = 72 * time.Hour + tt.Frame() + if tt.HasText("Resets in 5 h") { + t.Fatalf("far reset: %q", tt.Texts()) + } + defer func() { + if recover() == nil { + t.Fatal("zero limit accepted") + } + }() + accountTester(100, 100, func(c *ui.Context) { UsageQuota(c, []QuotaItem{{Label: "x"}}, UsageQuotaOptions{}) }) +} + +func TestUsageQuotaSnapshot(t *testing.T) { + snapshot(t, "usage_quota", 440, 230, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { + UsageQuota(c, quotaItems, UsageQuotaOptions{ResetAt: c.Now().Add(5*time.Hour + time.Minute)}) + }) + }) +} + +func TestUsageQuotaRejectsNonFiniteValues(t *testing.T) { + for _, tc := range []struct{ used, limit, warn float64 }{ + {1, 10, math.NaN()}, {math.Inf(1), 10, 0}, {1, math.Inf(1), 0}, {math.NaN(), 10, 0}, {1, math.NaN(), 0}, + } { + t.Run(fmt.Sprint(tc), func(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("non-finite quota accepted") + } + }() + accountTester(500, 300, func(c *ui.Context) { + UsageQuota(c, []QuotaItem{{Label: "Calls", Used: tc.used, Limit: tc.limit}}, UsageQuotaOptions{Warn: tc.warn}) + }) + }) + } +} diff --git a/user_menu.go b/user_menu.go new file mode 100644 index 0000000..0bef3bb --- /dev/null +++ b/user_menu.go @@ -0,0 +1,110 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// UserStatus is the presence a user shows to others. +type UserStatus int + +const ( + PresenceOnline UserStatus = iota + PresenceAway + PresenceBusy + PresenceInvisible +) + +// UserMenuOptions configure a UserMenu. Name is required. +type UserMenuOptions struct { + Name, Email string + Avatar *ui.Bitmap + Disabled bool +} + +// UserMenuResult reports presence, settings and sign-out events. +type UserMenuResult struct { + Element *ui.Element + changed, settings, leave bool +} + +// StatusChanged reports a new presence in *status. +func (r UserMenuResult) StatusChanged() bool { return r.changed } + +// Settings reports a request to open settings. +func (r UserMenuResult) Settings() bool { return r.settings } + +// SignOut reports a request to sign out. +func (r UserMenuResult) SignOut() bool { return r.leave } + +// UserMenu opens presence, settings and sign-out actions. +func UserMenu(c *ui.Context, status *UserStatus, opts UserMenuOptions) UserMenuResult { + if opts.Name == "" { + panic("mujica: UserMenu needs a Name") + } + if *status < PresenceOnline || *status > PresenceInvisible { + panic(fmt.Sprintf("mujica: UserMenu status %d is unknown", *status)) + } + k := Tokens(c) + var r UserMenuResult + word := msg(c, fmt.Sprintf("user_menu.status%d", *status)) + b := ui.ButtonBase(c).Role(ui.RoleMenuButton).Size(36, 36).Radius(18).Disabled(opts.Disabled). + Label(fmt.Sprintf(msg(c, "user_menu.label"), opts.Name, word)) + r.Element = b + internal.Ring(b, k.Focus, 18) + open := ui.Local(b, "open", func() bool { return false }) + disabled := b.IsDisabled() + if disabled { + *open = false + } else if b.Clicked() || b.Shortcut(0, ui.KeyDown) { + *open = true + } + st := *status + b.Children(func() { Avatar(c, opts.Name, AvatarOptions{Image: opts.Avatar, Size: 32}) }) + b.DrawOver(func(p *ui.Painter, rect ui.Rect) { userMenuDot(p, k, rect.X+rect.W-6, rect.Y+rect.H-6, st) }) + popupMenuOpen(c, b, open, func(m *PopupMenu) { + head := opts.Name + if opts.Email != "" { + head += " · " + opts.Email + } + m.Group(head, func() { + for s := PresenceOnline; s <= PresenceInvisible; s++ { + on := s == *status + if m.Check(msg(c, fmt.Sprintf("user_menu.status%d", s)), &on, PopupMenuItemOptions{}) && s != *status && !disabled { + *status, r.changed = s, true + slog.Debug("mujica: user_menu status", "status", s) + } + } + }) + m.Separator() + r.settings = m.Item(msg(c, "user_menu.settings"), PopupMenuItemOptions{}) && !disabled + r.leave = m.Item(msg(c, "user_menu.sign_out"), PopupMenuItemOptions{}) && !disabled + }) + if r.settings || r.leave { + slog.Debug("mujica: user_menu chose", "settings", r.settings, "sign_out", r.leave) + } + return r +} + +// userMenuDot distinguishes presence using shapes and colors. +func userMenuDot(p *ui.Painter, k theme.Tokens, cx, cy float32, s UserStatus) { + const d = 11 + r := ui.Rect{X: cx - d/2, Y: cy - d/2, W: d, H: d} + p.Fill(ui.Rect{X: r.X - 2, Y: r.Y - 2, W: d + 4, H: d + 4}, k.Surface, d) + switch s { + case PresenceOnline: + p.Fill(r, k.Success, d) + case PresenceAway: + p.Stroke(r, k.Warning, d, 3) + case PresenceBusy: + p.Fill(r, k.Danger, d) + p.Fill(ui.Rect{X: r.X + 2.5, Y: cy - 1, W: d - 5, H: 2}, k.Surface, 0) + default: + p.Stroke(r, k.TextMuted, d, 1.5) + } +} diff --git a/user_menu_test.go b/user_menu_test.go new file mode 100644 index 0000000..71505f6 --- /dev/null +++ b/user_menu_test.go @@ -0,0 +1,76 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestUserMenuStatusSettingsSignOut(t *testing.T) { + status := PresenceOnline + var changes, settings, leaves int + tt := accountTester(500, 400, func(c *ui.Context) { + r := UserMenu(c, &status, UserMenuOptions{Name: "Sakiko Togawa", Email: "sakiko@court"}) + if r.StatusChanged() { + changes++ + } + if r.Settings() { + settings++ + } + if r.SignOut() { + leaves++ + } + }) + if err := tt.Click("Sakiko Togawa, Online"); err != nil { + t.Fatal(err) + } + if !tt.HasText("Sakiko Togawa · sakiko@court") { + t.Fatalf("header: %q", tt.Texts()) + } + _ = tt.Click("Do not disturb") + if status != PresenceBusy || changes != 1 { + t.Fatalf("status %v", status) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) + _ = tt.Click("Settings") + _ = tt.Click("Sakiko Togawa, Do not disturb") + _ = tt.Click("Sign out") + if settings != 1 || leaves != 1 { + t.Fatalf("settings %d sign out %d", settings, leaves) + } +} + +func TestUserMenuSnapshot(t *testing.T) { + snapshot(t, "user_menu", 220, 90, func(c *ui.Context) { + ui.Row(c).Padding(20).Gap(16).Children(func() { + for s := PresenceOnline; s <= PresenceInvisible; s++ { + st := s + ui.Box(c).Key(int(s)).Children(func() { UserMenu(c, &st, UserMenuOptions{Name: "Sakiko Togawa"}) }) + } + }) + }) +} + +func TestUserMenuDisabledAndInvalidStatus(t *testing.T) { + status, disabled, actions := PresenceOnline, false, 0 + tt := accountTester(600, 500, func(c *ui.Context) { + ui.Column(c).Disabled(disabled).Children(func() { + r := UserMenu(c, &status, UserMenuOptions{Name: "User"}) + if r.StatusChanged() || r.Settings() || r.SignOut() { + actions++ + } + }) + }) + if err := tt.Click("User, Online"); err != nil { + t.Fatal(err) + } + disabled = true + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyEnter) + if status != PresenceOnline || actions != 0 || tt.HasText("Sign out") { + t.Fatal("disabled user menu acts") + } + status = UserStatus(99) + accountPanics(t, func(c *ui.Context) { UserMenu(c, &status, UserMenuOptions{Name: "User"}) }) +} diff --git a/whats_new_dialog.go b/whats_new_dialog.go new file mode 100644 index 0000000..e83e96d --- /dev/null +++ b/whats_new_dialog.go @@ -0,0 +1,99 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// ChangeKind tags a release note. +type ChangeKind int + +const ( + ChangeNew ChangeKind = iota + ChangeImproved + ChangeFixed +) + +// ReleaseChange is one line of release notes. +type ReleaseChange struct { + Kind ChangeKind + Text string +} + +// Release is a version's notes. +type Release struct { + Version, Date string + Changes []ReleaseChange +} + +// WhatsNewDialogOptions configure a WhatsNewDialog. +type WhatsNewDialogOptions struct { + // Title replaces the built-in "What's new". + Title string +} + +// WhatsNewDialog displays release notes and a persistent hide choice. +func WhatsNewDialog(c *ui.Context, open *bool, hide *bool, releases []Release, opts WhatsNewDialogOptions) ChoiceResult { + if len(releases) == 0 { + panic("mujica: WhatsNewDialog needs releases") + } + for _, rel := range releases { + if rel.Version == "" { + panic("mujica: Release needs a Version") + } + for _, change := range rel.Changes { + if change.Kind < ChangeNew || change.Kind > ChangeFixed || change.Text == "" { + panic("mujica: ReleaseChange needs a valid Kind and Text") + } + } + } + if ui.Box(c).IsDisabled() { + *open = false + } + k := Tokens(c) + d := Density(c) + title := opts.Title + if title == "" { + title = msg(c, "whats_new_dialog.title") + } + var r ChoiceResult + was := *open + before := *hide + r.Element = Dialog(c, open, DialogOptions{Title: title, Width: 480, Decorated: true, Actions: func() { + Checkbox(c, hide, msg(c, "whats_new_dialog.hide"), CheckboxOptions{}) + ui.Box(c).Grow(1) + if Button(c, msg(c, "whats_new_dialog.close"), ButtonOptions{}).AutoFocus().Clicked() { + *open = false + } + }}, func() { + for i, rel := range releases { + if rel.Version == "" { + panic("mujica: Release needs a Version") + } + ui.Column(c).Key(i).Gap(d.Unit() * 2).Role(ui.RoleGroup).Label(rel.Version).Children(func() { + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.End).Children(func() { + ui.Text(c, rel.Version).Font(theme.SerifFont).FontSize(FontSize(c, theme.H3Size)).FontWeight(600) + if rel.Date != "" { + ui.Text(c, rel.Date).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)) + } + }) + for _, ch := range rel.Changes { + ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Start).Children(func() { + tone := map[ChangeKind]BadgeTone{ChangeNew: BadgeAccent, ChangeImproved: BadgeInfo, ChangeFixed: BadgeSuccess}[ch.Kind] + Badge(c, BadgeOptions{Text: msg(c, fmt.Sprintf("whats_new_dialog.kind%d", ch.Kind)), Tone: tone}, nil).Shrink(0) + ui.Text(c, ch.Text).Grow(1).MinWidth(0) + }) + } + }) + } + }) + r.changed = *hide != before + if was && !*open { + slog.Debug("mujica: whats_new_dialog close", "hide", *hide) + } + return r +} diff --git a/whats_new_dialog_test.go b/whats_new_dialog_test.go new file mode 100644 index 0000000..dc716a6 --- /dev/null +++ b/whats_new_dialog_test.go @@ -0,0 +1,67 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +var releases = []Release{ + {Version: "1.1", Date: "2026-10-05", Changes: []ReleaseChange{{ChangeNew, "500 components."}, {ChangeFixed, "Snapshots no longer drift."}}}, + {Version: "1.0", Changes: []ReleaseChange{{ChangeImproved, "Court-gothic theme."}}}, +} + +func TestWhatsNewDialogListsAndHides(t *testing.T) { + open, hide, n := true, false, 0 + tt := accountTester(700, 600, func(c *ui.Context) { + if WhatsNewDialog(c, &open, &hide, releases, WhatsNewDialogOptions{}).Changed() { + n++ + } + }) + for _, want := range []string{"What's new", "1.1", "2026-10-05", "New", "Fixed", "Improved", "Court-gothic theme."} { + if !tt.HasText(want) { + t.Fatalf("missing %q in %q", want, tt.Texts()) + } + } + _ = tt.Click("Don't show again") + if !hide || n != 1 { + t.Fatal("hide not set") + } + _ = tt.Click("Got it") + if open { + t.Fatal("not closed") + } + open = true + tt.Frame() + tt.Key(0, ui.KeyEscape) + if open || !hide { + t.Fatal("escape did not close or lost hide") + } +} + +func TestWhatsNewDialogSnapshot(t *testing.T) { + snapshot(t, "whats_new_dialog", 560, 460, func(c *ui.Context) { + open := ui.Local(c.Root(), "open", func() bool { return true }) + hide := ui.Local(c.Root(), "hide", func() bool { return false }) + WhatsNewDialog(c, open, hide, releases, WhatsNewDialogOptions{}) + }) +} + +func TestWhatsNewDialogDisabledParentCloses(t *testing.T) { + open, hide, disabled := true, false, false + tt := accountTester(700, 600, func(c *ui.Context) { + ui.Column(c).Disabled(disabled).Children(func() { WhatsNewDialog(c, &open, &hide, releases, WhatsNewDialogOptions{}) }) + }) + disabled = true + tt.Frame() + if open || hide || tt.HasText("What's new") { + t.Fatal("disabled parent retains dialog") + } +} + +func TestWhatsNewDialogInvalidChange(t *testing.T) { + open, hide := true, false + accountPanics(t, func(c *ui.Context) { + WhatsNewDialog(c, &open, &hide, []Release{{Version: "1", Changes: []ReleaseChange{{Kind: ChangeKind(99), Text: "Change"}}}}, WhatsNewDialogOptions{}) + }) +} diff --git a/workspace_switcher.go b/workspace_switcher.go new file mode 100644 index 0000000..6fcd0e2 --- /dev/null +++ b/workspace_switcher.go @@ -0,0 +1,141 @@ +package mujica + +import ( + "fmt" + "log/slog" + "slices" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// Workspace is a place a user can switch to. +type Workspace struct { + ID, Name string + // Detail supplies the workspace summary. + Detail string +} + +// WorkspaceSwitcherOptions configure a WorkspaceSwitcher. +type WorkspaceSwitcherOptions struct { + Disabled bool +} + +type workspaceSwitcherState struct { + open bool + query string + cursor int + source []Workspace + hits []int + filtered string + ready bool +} + +// WorkspaceSwitcher filters workspaces and changes the current selection. +func WorkspaceSwitcher(c *ui.Context, current *string, spaces []Workspace, opts WorkspaceSwitcherOptions) ChoiceResult { + var cur *Workspace + for i := range spaces { + if spaces[i].ID == "" || spaces[i].Name == "" { + panic("mujica: Workspace needs an ID and a Name") + } + if spaces[i].ID == *current { + cur = &spaces[i] + } + } + if cur == nil { + panic(fmt.Sprintf("mujica: WorkspaceSwitcher current %q is not a workspace", *current)) + } + var r ChoiceResult + b := accountTrigger(c, cur.Name, cur.Detail, nil, opts.Disabled) + r.Element = b + st := ui.Local(b, "ws", func() workspaceSwitcherState { return workspaceSwitcherState{} }) + disabled := b.IsDisabled() + if disabled { + st.open = false + } else if b.Clicked() || b.Shortcut(0, ui.KeyDown) { + st.open, st.query, st.cursor = true, "", 0 + } + choose := func(i int) { + if !disabled && spaces[i].ID != *current { + *current, r.changed = spaces[i].ID, true + slog.Debug("mujica: workspace_switcher switched") + } + st.open = false + } + Popover(c, b, &st.open, PopoverOptions{Width: 300, Label: msg(c, "workspace_switcher.label")}, func() { + res := SearchInput(c, &st.query, SearchInputOptions{Placeholder: msg(c, "workspace_switcher.search"), Label: msg(c, "workspace_switcher.search")}) + in := res.Input + in.AutoFocus() + q := strings.ToLower(strings.TrimSpace(st.query)) + if !st.ready || st.filtered != q || !slices.Equal(st.source, spaces) { + st.source, st.filtered, st.ready = slices.Clone(spaces), q, true + st.hits, st.cursor = st.hits[:0], 0 + for i, w := range spaces { + if q == "" || strings.Contains(strings.ToLower(w.Name+" "+w.Detail), q) { + st.hits = append(st.hits, i) + } + } + } + hits := st.hits + st.cursor = max(0, min(st.cursor, len(hits)-1)) + before := st.cursor + switch { + case disabled || in.IsDisabled() || len(hits) == 0: + case in.Shortcut(0, ui.KeyDown): + st.cursor = (st.cursor + 1) % len(hits) + case in.Shortcut(0, ui.KeyUp): + st.cursor = (st.cursor + len(hits) - 1) % len(hits) + } + if res.Submitted() && len(hits) > 0 { + choose(hits[st.cursor]) + } + if len(hits) == 0 { + ui.Text(c, msg(c, "workspace_switcher.none")).TextColor(Tokens(c).TextMuted).Padding(Density(c).Unit() * 2).Role(ui.RoleStatus) + } + list := accountRows(c, len(hits), func(n int) any { return spaces[hits[n]].ID }, func(n int) { + i := hits[n] + row := workspaceRow(c, spaces[i], spaces[i].ID == *current, n == st.cursor) + if row.Clicked() && !row.IsDisabled() { + choose(i) + } + }) + if len(hits) > 8 && (st.cursor != before || res.Changed()) { + rows := ui.Local(list, "rows", func() ui.ListState { return ui.ListState{} }) + rows.ScrollIntoView(st.cursor) + } + }) + return r +} + +// workspaceRow displays a clickable workspace with a selection mark. +func workspaceRow(c *ui.Context, w Workspace, current, cursor bool) *ui.Element { + k := Tokens(c) + d := Density(c) + b := ui.ButtonBase(c).Key(w.ID).MinHeight(d.ControlHeight()).Padding(d.Unit(), d.Unit()*2).Gap(d.Unit() * 2). + Justify(ui.Start).Radius(theme.ControlRadius).Role(ui.RoleListItem).Label(w.Name).Selected(current) + internal.Ring(b, k.Focus, theme.ControlRadius) + switch { + case cursor || current: + b.Background(k.Selection) + case b.Hovered(): + b.Background(k.SurfaceHover) + } + b.Children(func() { + ui.Box(c).Size(16, 16).Shrink(0).Children(func() { + if current { + ui.Icon(c, icons.Must("check")).Size(16, 16).TextColor(k.AccentText) + } + }) + ui.Column(c).Grow(1).MinWidth(0).Children(func() { + ui.Text(c, w.Name).SingleLine().Ellipsis("…") + if w.Detail != "" { + ui.Text(c, w.Detail).TextColor(k.TextMuted).FontSize(FontSize(c, theme.CaptionSize)).SingleLine() + } + }) + }) + return b +} diff --git a/workspace_switcher_test.go b/workspace_switcher_test.go new file mode 100644 index 0000000..aa8e8b6 --- /dev/null +++ b/workspace_switcher_test.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +var switcherSpaces = []Workspace{{ID: "ave", Name: "Ave Mujica", Detail: "5 members"}, {ID: "mygo", Name: "MyGO!!!!!", Detail: "5 members"}, {ID: "crychic", Name: "CRYCHIC", Detail: "Archived"}} + +func TestWorkspaceSwitcherSearchAndSwitch(t *testing.T) { + cur, n := "ave", 0 + tt := accountTester(500, 500, func(c *ui.Context) { + if WorkspaceSwitcher(c, &cur, switcherSpaces, WorkspaceSwitcherOptions{}).Changed() { + n++ + } + }) + if err := tt.Click("Ave Mujica"); err != nil { + t.Fatal(err) + } + if !tt.Focused("Find a workspace") || !tt.HasText("CRYCHIC") { + t.Fatalf("open: %q", tt.Texts()) + } + tt.Type("arch") + if tt.HasText("MyGO!!!!!") || !tt.HasText("CRYCHIC") { + t.Fatalf("search: %q", tt.Texts()) + } + tt.Key(0, ui.KeyEnter) + if cur != "crychic" || n != 1 || tt.HasText("Find a workspace") { + t.Fatalf("enter: %q", cur) + } + _ = tt.Click("CRYCHIC") + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyEnter) + if cur != "mygo" || n != 2 { + t.Fatalf("arrow: %q", cur) + } + _ = tt.Click("MyGO!!!!!") + tt.Type("zzz") + if !tt.HasText("No workspace matches") { + t.Fatal("no empty state") + } + tt.Key(0, ui.KeyEscape) + if tt.HasText("No workspace matches") || cur != "mygo" { + t.Fatal("escape did not close") + } +} + +func TestWorkspaceSwitcherSnapshot(t *testing.T) { + snapshot(t, "workspace_switcher", 300, 90, func(c *ui.Context) { + cur := ui.Local(c.Root(), "cur", func() string { return "ave" }) + ui.Column(c).Padding(16).AlignItems(ui.Start).Children(func() { WorkspaceSwitcher(c, cur, switcherSpaces, WorkspaceSwitcherOptions{}) }) + }) +} + +func TestWorkspaceSwitcherLargeDataBuildsVisibleRows(t *testing.T) { + items := make([]Workspace, 100000) + for i := range items { + items[i] = Workspace{ID: fmt.Sprint(i), Name: fmt.Sprintf("Workspace %d", i)} + } + cur := "0" + tt := accountTester(500, 500, func(c *ui.Context) { WorkspaceSwitcher(c, &cur, items, WorkspaceSwitcherOptions{}) }) + if err := tt.Click("Workspace 0"); err != nil { + t.Fatal(err) + } + if n := len(tt.Texts()); n > 100 { + t.Fatalf("built %d text elements for a bounded viewport", n) + } + tt.Type("99999") + tt.Key(0, ui.KeyEnter) + if cur != "99999" { + t.Fatalf("large-list search choice %q", cur) + } +} + +func TestWorkspaceSwitcherDisablingClosesAndInvalidCurrent(t *testing.T) { + cur, disabled := "ave", false + tt := accountTester(600, 500, func(c *ui.Context) { + ui.Column(c).Disabled(disabled).Children(func() { WorkspaceSwitcher(c, &cur, switcherSpaces, WorkspaceSwitcherOptions{}) }) + }) + if err := tt.Click("Ave Mujica"); err != nil { + t.Fatal(err) + } + disabled = true + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyEnter) + if tt.HasText("Find a workspace") || cur != "ave" { + t.Fatal("disabled workspace popup stays active") + } + cur = "unknown" + accountPanics(t, func(c *ui.Context) { WorkspaceSwitcher(c, &cur, switcherSpaces, WorkspaceSwitcherOptions{}) }) +}