From d10072d6593b39e5a871fbce8ca024d2737429b7 Mon Sep 17 00:00:00 2001 From: Leigh MacDonald Date: Sun, 26 Feb 2023 02:20:48 -0700 Subject: [PATCH] Initial list config ui created. --- lists.go | 6 +- model/settings.go | 37 ++++++--- translations/active.en.yaml | 3 + translations/translations.go | 1 + ui/lists.go | 155 ++++++++++++++++++++++++++++++++++- ui/ui.go | 20 ++++- ui/widget.go | 2 + 7 files changed, 203 insertions(+), 21 deletions(-) diff --git a/lists.go b/lists.go index 6ab2107..a2ec639 100644 --- a/lists.go +++ b/lists.go @@ -29,12 +29,12 @@ func fetchURL(ctx context.Context, client http.Client, url string) ([]byte, erro return body, nil } -func downloadLists(ctx context.Context, lists []model.ListConfig) ([]rules.PlayerListSchema, []rules.RuleSchema) { +func downloadLists(ctx context.Context, lists model.ListConfigCollection) ([]rules.PlayerListSchema, []rules.RuleSchema) { var playerLists []rules.PlayerListSchema var rulesLists []rules.RuleSchema mu := &sync.RWMutex{} client := http.Client{} - downloadFn := func(u model.ListConfig) error { + downloadFn := func(u *model.ListConfig) error { body, errFetch := fetchURL(ctx, client, u.URL) if errFetch != nil { return errors.Wrapf(errFetch, "Failed to fetch player list: %s", u.URL) @@ -67,7 +67,7 @@ func downloadLists(ctx context.Context, lists []model.ListConfig) ([]rules.Playe continue } wg.Add(1) - go func(lc model.ListConfig) { + go func(lc *model.ListConfig) { defer wg.Done() if errDL := downloadFn(lc); errDL != nil { log.Printf("Failed to download list: %v", errDL) diff --git a/model/settings.go b/model/settings.go index d5816be..59fd045 100644 --- a/model/settings.go +++ b/model/settings.go @@ -38,6 +38,7 @@ const ( type ListConfig struct { ListType ListType `yaml:"type"` + Name string `yaml:"name"` Enabled bool `yaml:"enabled"` URL string `yaml:"url"` } @@ -68,17 +69,27 @@ type Settings struct { // eg: -> ~/.local/share/Steam/userdata/123456789/config/localconfig.vdf SteamDir string `yaml:"steam_dir"` // Path to tf2 mod (C:\Program Files (x86)\Steam\steamapps\common\Team Fortress 2\tf) - TF2Dir string `yaml:"tf2_dir"` - ApiKey string `yaml:"api_key"` - DisconnectedTimeout string `yaml:"disconnected_timeout"` - DiscordPresenceEnabled bool `yaml:"discord_presence_enabled"` - KickerEnabled bool `yaml:"kicker_enabled"` - ChatWarningsEnabled bool `yaml:"chat_warnings_enabled"` - PartyWarningsEnabled bool `yaml:"party_warnings_enabled"` - Lists []ListConfig `yaml:"lists"` - Links []LinkConfig `yaml:"links"` - RconStatic bool `yaml:"rcon_static"` - Rcon RCONConfigProvider `yaml:"-"` + TF2Dir string `yaml:"tf2_dir"` + ApiKey string `yaml:"api_key"` + DisconnectedTimeout string `yaml:"disconnected_timeout"` + DiscordPresenceEnabled bool `yaml:"discord_presence_enabled"` + KickerEnabled bool `yaml:"kicker_enabled"` + ChatWarningsEnabled bool `yaml:"chat_warnings_enabled"` + PartyWarningsEnabled bool `yaml:"party_warnings_enabled"` + Lists ListConfigCollection `yaml:"lists"` + Links []LinkConfig `yaml:"links"` + RconStatic bool `yaml:"rcon_static"` + Rcon RCONConfigProvider `yaml:"-"` +} + +type ListConfigCollection []*ListConfig + +func (list ListConfigCollection) AsAny() []any { + bl := make([]any, len(list)) + for i, r := range list { + bl[i] = r + } + return bl } func (s *Settings) GetSteamId() steamid.SID64 { @@ -90,7 +101,7 @@ func (s *Settings) GetSteamId() steamid.SID64 { return value } -func (s *Settings) AddList(config ListConfig) error { +func (s *Settings) AddList(config *ListConfig) error { s.Lock() defer s.Unlock() for _, known := range s.Lists { @@ -121,7 +132,7 @@ func NewSettings() (*Settings, error) { KickerEnabled: false, ChatWarningsEnabled: false, PartyWarningsEnabled: true, - Lists: []ListConfig{ + Lists: []*ListConfig{ { ListType: "tf2bd_playerlist", Enabled: false, diff --git a/translations/active.en.yaml b/translations/active.en.yaml index fe8bf77..8f36abf 100644 --- a/translations/active.en.yaml +++ b/translations/active.en.yaml @@ -4,6 +4,9 @@ label_launch: label_settings: one: Settings +label_list_config: + one: List Config + label_chat_log: one: Chat Log diff --git a/translations/translations.go b/translations/translations.go index 3194e6b..a8ba284 100644 --- a/translations/translations.go +++ b/translations/translations.go @@ -22,6 +22,7 @@ type Key string const ( LabelLaunch Key = "label_launch" LabelSettings Key = "label_settings" + LabelListConfig Key = "label_list_config" LabelChatLog Key = "label_chat_log" LabelConfigFolder Key = "label_config_folder" LabelHelp Key = "label_help" diff --git a/ui/lists.go b/ui/lists.go index dc321a8..ab80ff1 100644 --- a/ui/lists.go +++ b/ui/lists.go @@ -1,5 +1,154 @@ package ui -//func newRuleListConfigWindow() { -// -//} +import ( + "fmt" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/leighmacdonald/bd/model" + "github.com/leighmacdonald/bd/translations" + "github.com/pkg/errors" + "log" + "net/url" +) + +func newRuleListConfigDialog(parent fyne.Window, saveFn func(), settings *model.Settings) dialog.Dialog { + l := newBaseListWidget() + l.SetupList(func() fyne.CanvasObject { + return container.NewBorder( + nil, + nil, + nil, + container.NewHBox(widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func() { + + }), widget.NewButtonWithIcon("Delete", theme.DeleteIcon(), func() { + + })), + widget.NewLabel(""), + ) + }, func(i binding.DataItem, o fyne.CanvasObject) { + value := i.(binding.Untyped) + obj, _ := value.Get() + lc := obj.(*model.ListConfig) + rootContainer := o.(*fyne.Container) + + name := rootContainer.Objects[0].(*widget.Label) + name.Bind(binding.BindString(&lc.Name)) + + urlEntry := widget.NewEntryWithData(binding.BindString(&lc.URL)) + urlEntry.Validator = func(s string) error { + _, e := url.Parse(s) + return e + } + btnContainer := rootContainer.Objects[1].(*fyne.Container) + + editButton := btnContainer.Objects[0].(*widget.Button) + editButton.OnTapped = func() { + nameEntry := widget.NewEntryWithData(binding.BindString(&lc.Name)) + enabledEntry := widget.NewCheckWithData("Enabled", binding.BindBool(&lc.Enabled)) + d := dialog.NewForm("Edit item", "Confirm", "Dismiss", []*widget.FormItem{ + {Text: "Name", Widget: nameEntry}, + {Text: "Url", Widget: urlEntry}, + {Text: "Enabled", Widget: enabledEntry}, + }, func(valid bool) { + if !valid { + return + } + saveFn() + name.SetText(nameEntry.Text) + }, parent) + sz := d.MinSize() + sz.Width = defaultDialogueWidth + d.Resize(sz) + d.Show() + } + deleteButton := btnContainer.Objects[1].(*widget.Button) + deleteButton.OnTapped = func() { + confirm := dialog.NewConfirm("Delete Confirmation", fmt.Sprintf("Are you are you want to delete the list?: %s", lc.Name), func(b bool) { + if !b { + return + } + settings.Lock() + var lists model.ListConfigCollection + for _, list := range settings.Lists { + if list == lc { + continue + } + lists = append(lists, list) + } + settings.Lists = lists + settings.Unlock() + if errReload := l.Reload(settings.Lists.AsAny()); errReload != nil { + log.Printf("Failed to reload: %v\n", errReload) + } + saveFn() + }, parent) + confirm.Show() + } + + }) + + toolBar := container.NewBorder( + nil, + nil, nil, container.NewHBox( + widget.NewButtonWithIcon("Add", theme.ContentAddIcon(), func() { + newNameEntry := widget.NewEntryWithData(binding.NewString()) + newName := widget.NewFormItem("Name", newNameEntry) + newNameEntry.Validator = func(s string) error { + if len(s) == 0 { + return errors.New("Name cannot be empty") + } + return nil + } + newUrlEntry := widget.NewEntryWithData(binding.NewString()) + newUrlEntry.Validator = func(s string) error { + _, e := url.Parse(s) + if e != nil { + return errors.New("Invalid URL") + } + return nil + } + newUrl := widget.NewFormItem("Update URL", newUrlEntry) + newEnabledEntry := widget.NewCheckWithData("", binding.NewBool()) + newEnabled := widget.NewFormItem("Enabled", newEnabledEntry) + inputForm := dialog.NewForm("Import URL", "Confirm", "Dismisake s", []*widget.FormItem{ + newName, newUrl, newEnabled, + }, func(b bool) { + if !b { + return + } + lc := &model.ListConfig{ + ListType: "", + Name: newNameEntry.Text, + Enabled: newEnabledEntry.Checked, + URL: newUrlEntry.Text, + } + settings.Lock() + settings.Lists = append(settings.Lists, lc) + settings.Unlock() + if errAppend := l.boundList.Append(lc); errAppend != nil { + log.Printf("Failed to update config list: %v", errAppend) + } + saveFn() + }, parent) + sz := inputForm.MinSize() + sz.Width = defaultDialogueWidth + inputForm.Resize(sz) + inputForm.Show() + })), + container.NewHBox()) + l.SetContent(container.NewBorder(toolBar, nil, nil, nil, l.Widget())) + if errSet := l.Reload(settings.Lists.AsAny()); errSet != nil { + log.Printf("failed to load lists") + } + settingsWindow := dialog.NewCustom("List Config", translations.One(translations.LabelClose), l.Widget(), parent) + sz := settingsWindow.MinSize() + sz.Width = defaultDialogueWidth + sz.Height = 500 + settingsWindow.Resize(sz) + //settingsWindow.Resize(fyne.NewSize(5050, 700)) + return settingsWindow +} diff --git a/ui/ui.go b/ui/ui.go index 96dabdf..9ea5869 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -54,6 +54,7 @@ type Ui struct { rootWindow fyne.Window chatWindow fyne.Window settingsDialog dialog.Dialog + listsDialog dialog.Dialog aboutDialog dialog.Dialog boundSettings boundSettings settings *model.Settings @@ -101,13 +102,15 @@ func New(settings *model.Settings) UserInterface { playerSortDir: playerSortStatus, } - ui.settingsDialog = ui.newSettingsDialog(rootWindow, func() { + saveFunc := func() { if errSave := settings.Save(); errSave != nil { log.Printf("Failed to save config file: %v\n", errSave) return } log.Println("Settings saved successfully") - }) + } + ui.settingsDialog = ui.newSettingsDialog(rootWindow, saveFunc) + ui.listsDialog = newRuleListConfigDialog(rootWindow, saveFunc, settings) ui.aboutDialog = ui.createAboutDialog(rootWindow) ui.playerList = ui.createPlayerList() ui.userMessageList = ui.createGameChatMessageList() @@ -393,6 +396,7 @@ func (ui *Ui) newMainMenu() *fyne.MainMenu { shortCutChat := &desktop.CustomShortcut{KeyName: fyne.KeyC, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift} shortCutFolder := &desktop.CustomShortcut{KeyName: fyne.KeyE, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift} shortCutSettings := &desktop.CustomShortcut{KeyName: fyne.KeyS, Modifier: fyne.KeyModifierControl} + shortCutLists := &desktop.CustomShortcut{KeyName: fyne.KeyL, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift} shortCutQuit := &desktop.CustomShortcut{KeyName: fyne.KeyQ, Modifier: fyne.KeyModifierControl} shortCutHelp := &desktop.CustomShortcut{KeyName: fyne.KeyH, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift} shortCutAbout := &desktop.CustomShortcut{KeyName: fyne.KeyA, Modifier: fyne.KeyModifierControl | fyne.KeyModifierShift} @@ -409,6 +413,9 @@ func (ui *Ui) newMainMenu() *fyne.MainMenu { ui.rootWindow.Canvas().AddShortcut(shortCutSettings, func(shortcut fyne.Shortcut) { ui.settingsDialog.Show() }) + ui.rootWindow.Canvas().AddShortcut(shortCutLaunch, func(shortcut fyne.Shortcut) { + ui.listsDialog.Show() + }) ui.rootWindow.Canvas().AddShortcut(shortCutQuit, func(shortcut fyne.Shortcut) { ui.application.Quit() }) @@ -447,6 +454,12 @@ func (ui *Ui) newMainMenu() *fyne.MainMenu { Action: ui.settingsDialog.Show, Icon: theme.SettingsIcon(), }, + &fyne.MenuItem{ + Shortcut: shortCutLists, + Label: translations.One(translations.LabelListConfig), + Action: ui.listsDialog.Show, + Icon: theme.StorageIcon(), + }, fyne.NewMenuItemSeparator(), &fyne.MenuItem{ Icon: theme.ContentUndoIcon(), @@ -513,6 +526,9 @@ func (ui *Ui) newToolbar(chatFunc func(), settingsFunc func(), aboutFunc func()) widget.NewToolbarAction(theme.MailComposeIcon(), chatFunc), widget.NewToolbarSeparator(), widget.NewToolbarAction(theme.SettingsIcon(), settingsFunc), + widget.NewToolbarAction(theme.StorageIcon(), func() { + ui.listsDialog.Show() + }), widget.NewToolbarAction(theme.FolderOpenIcon(), func() { platform.OpenFolder(ui.settings.ConfigRoot()) }), diff --git a/ui/widget.go b/ui/widget.go index ad6a12e..899ebf8 100644 --- a/ui/widget.go +++ b/ui/widget.go @@ -11,6 +11,8 @@ import ( "sync" ) +const defaultDialogueWidth = 600 + type baseListWidget struct { list *widget.List boundList binding.ExternalUntypedList