Merge Settings & Account
This commit is contained in:
108 files changed
+6501
-2
No files matched your search
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)`,
|
||||
}
|
||||
@@ -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()
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user