native dialogs
This commit is contained in:
parent
47e7c30604
commit
fda504005e
3 changed files with 87 additions and 73 deletions
150
main.go
150
main.go
|
|
@ -13,25 +13,22 @@ import (
|
|||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
simpledialog "github.com/sqweek/dialog"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/emersion/go-autostart"
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/gregorybednov/lbc/cli"
|
||||
toml "github.com/pelletier/go-toml/v2"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
//go:embed icon.png
|
||||
var iconPNG []byte
|
||||
|
||||
func appIcon() fyne.Resource {
|
||||
return fyne.NewStaticResource("icon.png", iconPNG)
|
||||
}
|
||||
|
||||
/* --------------------------- Конфиг лончера --------------------------- */
|
||||
|
||||
type LauncherCfg struct {
|
||||
|
|
@ -101,8 +98,18 @@ func (w *uiWriter) Write(p []byte) (int, error) {
|
|||
|
||||
func main() {
|
||||
st := &AppState{}
|
||||
must(initConfig(st))
|
||||
must(acquireSingleInstanceLock(st))
|
||||
|
||||
err := initConfig(st)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = acquireSingleInstanceLock(st)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
defer releaseSingleInstanceLock(st)
|
||||
|
||||
exe, _ := os.Executable()
|
||||
|
|
@ -115,6 +122,7 @@ func main() {
|
|||
|
||||
a := app.New()
|
||||
w := a.NewWindow("LBС Launcher")
|
||||
|
||||
st.app, st.win = a, w
|
||||
|
||||
// Статус + логи
|
||||
|
|
@ -124,18 +132,15 @@ func main() {
|
|||
st.logArea.Disable() // только для чтения
|
||||
|
||||
homeEntry := widget.NewEntry()
|
||||
homeEntry.SetPlaceHolder("Каталог HOME узла (будут data/, config/)")
|
||||
homeEntry.SetPlaceHolder("Полный путь до каталога (там будут данные и настройки)")
|
||||
homeEntry.SetText(st.cfg.NodeHome)
|
||||
btnPickHome := widget.NewButton("Выбрать HOME…", func() {
|
||||
dd := dialog.NewFolderOpen(func(lu fyne.ListableURI, err error) {
|
||||
if lu == nil || err != nil {
|
||||
return
|
||||
}
|
||||
st.cfg.NodeHome = lu.Path()
|
||||
btnPickHome := widget.NewButton("Выбрать каталог…", func() {
|
||||
directory, err := simpledialog.Directory().Title("Выбрать каталог с настройками и данными").Browse()
|
||||
if err == nil {
|
||||
st.cfg.NodeHome = directory
|
||||
homeEntry.SetText(st.cfg.NodeHome)
|
||||
_ = saveConfig(st)
|
||||
}, w)
|
||||
dd.Show()
|
||||
}
|
||||
})
|
||||
|
||||
topControls := container.NewVBox(
|
||||
|
|
@ -151,7 +156,7 @@ func main() {
|
|||
go func() {
|
||||
w := &uiWriter{st}
|
||||
if err := cli.ExecuteWithArgs([]string{"init", "genesis"}, homeEntry.Text, w, w); err != nil {
|
||||
st.alert("Ошибка: " + err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Произошла ошибка").Error()
|
||||
} else {
|
||||
st.notify("init genesis: готово")
|
||||
}
|
||||
|
|
@ -170,33 +175,45 @@ func main() {
|
|||
exportBtn := widget.NewButton("Экспортировать genesis.json…", func() {
|
||||
// genesis лежит в HOME/config/genesis.json
|
||||
if homeEntry.Text == "" {
|
||||
st.alert("Сначала укажи HOME каталóg узла.")
|
||||
simpledialog.Message("%s", "Сначала укажите домашний каталог узла").Title("Не найден HOME").Error()
|
||||
return
|
||||
}
|
||||
src := filepath.Join(homeEntry.Text, "config", "genesis.json")
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
st.alert("Не найден файл: " + src)
|
||||
simpledialog.Message("%s", "Не найден файл"+src).Title("Файл не найден").Error()
|
||||
return
|
||||
}
|
||||
fd := dialog.NewFileSave(func(uw fyne.URIWriteCloser, err error) {
|
||||
if uw == nil || err != nil {
|
||||
return
|
||||
}
|
||||
defer uw.Close()
|
||||
f, e := os.Open(src)
|
||||
if e != nil {
|
||||
st.alert("Ошибка чтения genesis: " + e.Error())
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if _, e = io.Copy(uw, f); e != nil {
|
||||
st.alert("Ошибка записи: " + e.Error())
|
||||
return
|
||||
}
|
||||
st.notify("Готово: экспортирован genesis.json")
|
||||
}, w)
|
||||
fd.SetFileName("genesis.json")
|
||||
fd.Show()
|
||||
|
||||
savePath, err := simpledialog.File().Filter("JSON files", "json").Title("Экспорт данных о сети").Save()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// открыть исходный файл
|
||||
f, e := os.Open(src)
|
||||
if e != nil {
|
||||
simpledialog.Message("Ошибка чтения genesis: %s", e.Error()).Title("Ошибка").Error()
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// создать файл для сохранения
|
||||
uw, e := os.Create(savePath)
|
||||
if e != nil {
|
||||
simpledialog.Message("Ошибка создания файла: %s", e.Error()).Title("Ошибка").Error()
|
||||
return
|
||||
}
|
||||
defer uw.Close()
|
||||
|
||||
// копируем содержимое
|
||||
if _, e = io.Copy(uw, f); e != nil {
|
||||
simpledialog.Message("Ошибка записи: %s", e.Error()).Title("Ошибка").Error()
|
||||
return
|
||||
}
|
||||
|
||||
st.notify(fmt.Sprintf("Готово: экспортирован genesis.json -> %s", savePath))
|
||||
//fd.SetFileName("genesis.json")
|
||||
//fd.Show()
|
||||
})
|
||||
tabExportGenesis := container.NewVBox(
|
||||
widget.NewLabel("Экспорт genesis.json из текущего узла"),
|
||||
|
|
@ -207,13 +224,11 @@ func main() {
|
|||
genesisPathEntry := widget.NewEntry()
|
||||
genesisPathEntry.SetPlaceHolder("Путь к существующему genesis.json")
|
||||
pickGenesis := widget.NewButton("Выбрать genesis.json…", func() {
|
||||
fd := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
|
||||
if uc == nil || err != nil {
|
||||
return
|
||||
}
|
||||
genesisPathEntry.SetText(uc.URI().Path())
|
||||
}, w)
|
||||
fd.Show()
|
||||
filename, err := simpledialog.File().Filter("Genesis JSON file", "json").Load()
|
||||
if err == nil {
|
||||
genesisPathEntry.SetText(filename)
|
||||
}
|
||||
|
||||
})
|
||||
joinBtn := widget.NewButton("Создать узел-присоединение (init joiner)", func() {
|
||||
if !st.ensurePathsOk(homeEntry.Text) {
|
||||
|
|
@ -222,21 +237,21 @@ func main() {
|
|||
// 1) скопировать genesis.json в HOME/config/genesis.json
|
||||
src := genesisPathEntry.Text
|
||||
if src == "" {
|
||||
st.alert("Укажи путь к genesis.json")
|
||||
simpledialog.Message("%s", "Укажите путь к genesis.json").Title("Не найден файл").Error()
|
||||
return
|
||||
}
|
||||
dest := filepath.Join(homeEntry.Text, "config")
|
||||
_ = os.MkdirAll(dest, 0o755)
|
||||
destFile := filepath.Join(dest, "genesis.json")
|
||||
if err := fileCopy(src, destFile); err != nil {
|
||||
st.alert("Не удалось скопировать genesis.json: " + err.Error())
|
||||
simpledialog.Message("%s", "Не удалось скопировать genesis.json: "+err.Error()).Title("Ошибка генерации").Error()
|
||||
return
|
||||
}
|
||||
// 2) lbc init joiner
|
||||
go func() {
|
||||
w := &uiWriter{st}
|
||||
if err := cli.ExecuteWithArgs([]string{"init", "joiner", genesisPathEntry.Text}, homeEntry.Text, w, w); err != nil {
|
||||
st.alert("Ошибка: " + err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Произошла ошибка").Error()
|
||||
} else {
|
||||
st.notify("init joiner: готово")
|
||||
}
|
||||
|
|
@ -258,12 +273,12 @@ func main() {
|
|||
|
||||
loadCfgBtn := widget.NewButton("Загрузить config.toml", func() {
|
||||
if homeEntry.Text == "" {
|
||||
st.alert("Укажи HOME узла.")
|
||||
simpledialog.Message("%s", "Укажите домашнюю директорию узла").Title("Ошибка сохранения").Error()
|
||||
return
|
||||
}
|
||||
cfgp := filepath.Join(homeEntry.Text, "config", "config.toml")
|
||||
if cfg, err := loadTOML(cfgp); err != nil {
|
||||
st.alert("Чтение TOML: " + err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Ошибка чтения TOML").Error()
|
||||
} else {
|
||||
cfgPathLbl.SetText("Файл: " + cfgp)
|
||||
monikerEntry.SetText(cfg.Moniker)
|
||||
|
|
@ -278,13 +293,13 @@ func main() {
|
|||
})
|
||||
saveCfgBtn := widget.NewButton("Сохранить", func() {
|
||||
if homeEntry.Text == "" {
|
||||
st.alert("Укажи HOME узла.")
|
||||
simpledialog.Message("%s", "Укажите домашнюю директорию узла").Title("Ошибка сохранения").Error()
|
||||
return
|
||||
}
|
||||
cfgp := filepath.Join(homeEntry.Text, "config", "config.toml")
|
||||
cfg, err := loadTOML(cfgp) // мягко читаем всё, меняем только интересующие поля
|
||||
cfg, err := loadTOML(cfgp)
|
||||
if err != nil {
|
||||
st.alert("Чтение TOML: " + err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Ошибка чтения TOML").Error()
|
||||
return
|
||||
}
|
||||
cfg.Moniker = monikerEntry.Text
|
||||
|
|
@ -292,7 +307,7 @@ func main() {
|
|||
cfg.P2P.PersistentPeers = ppeersEntry.Text
|
||||
cfg.LogLevel = logLevelSel.Selected
|
||||
if err := saveTOML(cfgp, cfg); err != nil {
|
||||
st.alert("Запись TOML: " + err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Ошибка записи TOML").Error()
|
||||
return
|
||||
}
|
||||
st.notify("config.toml сохранён")
|
||||
|
|
@ -326,7 +341,7 @@ func main() {
|
|||
cbAutostart := widget.NewCheck("Запускать при входе в систему", func(v bool) {
|
||||
if v {
|
||||
if err := autoApp.Enable(); err != nil {
|
||||
st.alert("Автозапуск: " + err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Произошла ошибка автозапуска").Error()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
|
@ -352,7 +367,7 @@ func main() {
|
|||
|
||||
// Трей-меню
|
||||
if desk, ok := a.(desktop.App); ok {
|
||||
desk.SetSystemTrayIcon(appIcon())
|
||||
desk.SetSystemTrayIcon(fyne.NewStaticResource("icon.png", iconPNG))
|
||||
openItem := fyne.NewMenuItem("Открыть", func() { w.Show(); w.RequestFocus() })
|
||||
autoItem := fyne.NewMenuItem("Автозапуск", nil)
|
||||
autoItem.Checked = autoApp.IsEnabled()
|
||||
|
|
@ -360,7 +375,7 @@ func main() {
|
|||
ns := !autoItem.Checked
|
||||
if ns {
|
||||
if err := autoApp.Enable(); err != nil {
|
||||
st.alert(err.Error())
|
||||
simpledialog.Message("%s", err.Error()).Title("Произошла ошибка").Error()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
|
@ -370,7 +385,7 @@ func main() {
|
|||
st.cfg.AutoStart = ns
|
||||
_ = saveConfig(st)
|
||||
}
|
||||
quitItem := fyne.NewMenuItem("Выйти", func() { releaseSingleInstanceLock(st); a.Quit() })
|
||||
quitItem := fyne.NewMenuItem("Выход", func() { releaseSingleInstanceLock(st); a.Quit() })
|
||||
desk.SetSystemTrayMenu(fyne.NewMenu("", openItem, autoItem, fyne.NewMenuItemSeparator(), quitItem))
|
||||
}
|
||||
|
||||
|
|
@ -503,21 +518,21 @@ func (st *AppState) appendLog(s string) {
|
|||
st.logArea.CursorRow = 999999
|
||||
}()
|
||||
}
|
||||
func (st *AppState) alert(msg string) {
|
||||
dialog.ShowInformation("Сообщение", msg, st.win)
|
||||
}
|
||||
|
||||
func (st *AppState) notify(msg string) {
|
||||
st.app.SendNotification(&fyne.Notification{Title: "LBClient", Content: msg})
|
||||
}
|
||||
|
||||
func (st *AppState) ensurePathsOk(home string) bool {
|
||||
if home == "" {
|
||||
st.alert("Укажи HOME каталог узла")
|
||||
simpledialog.Message("%s", "Каталог узла не найден").Title("Не найден HOME").Error()
|
||||
return false
|
||||
}
|
||||
st.cfg.NodeHome = home
|
||||
_ = saveConfig(st)
|
||||
return true
|
||||
}
|
||||
|
||||
func fileCopy(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
|
|
@ -537,12 +552,3 @@ func fileCopy(src, dst string) error {
|
|||
}
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
/* --------------------------------- misc -------------------------------- */
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue