diff options
Diffstat (limited to 'cmd/nymdrop-reader/gui.go')
| -rw-r--r-- | cmd/nymdrop-reader/gui.go | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/cmd/nymdrop-reader/gui.go b/cmd/nymdrop-reader/gui.go new file mode 100644 index 0000000..d8e5383 --- /dev/null +++ b/cmd/nymdrop-reader/gui.go @@ -0,0 +1,135 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + + "nymdrop/internal/reader" +) + +// submissionEntry is one row in the submissions list: enough to render the +// row (label) and to load the full body on demand (path) without keeping +// every decrypted body in memory at once. +type submissionEntry struct { + label string + path string +} + +// runGUI is the non-technical front-end for nymdrop-reader: a journalist +// running this from a USB stick should never need a terminal. It drives the +// same reader.Instance the CLI uses, through the same Hooks contract, so +// there is exactly one implementation of the receive/decrypt/save logic. +func runGUI(cfg reader.Config) { + a := app.NewWithID("art.virebent.nymdrop-reader") + w := a.NewWindow("NymDrop Reader") + w.Resize(fyne.NewSize(900, 560)) + + statusLabel := widget.NewLabel("Starting...") + statusLabel.Wrapping = fyne.TextWrapWord + + addrEntry := widget.NewEntry() + addrEntry.Disable() + addrEntry.SetPlaceHolder("Nym inbox address will appear here once connected") + + outDirLabel := widget.NewLabel("") + + var submissions []submissionEntry + + content := widget.NewMultiLineEntry() + content.Wrapping = fyne.TextWrapWord + content.Disable() + + list := widget.NewList( + func() int { return len(submissions) }, + func() fyne.CanvasObject { return widget.NewLabel("template") }, + func(id widget.ListItemID, obj fyne.CanvasObject) { + obj.(*widget.Label).SetText(submissions[id].label) + }, + ) + list.OnSelected = func(id widget.ListItemID) { + if id < 0 || id >= len(submissions) { + return + } + data, err := os.ReadFile(submissions[id].path) + if err != nil { + content.SetText(fmt.Sprintf("error reading %s: %v", submissions[id].path, err)) + return + } + content.SetText(string(data)) + } + + hooks := reader.Hooks{ + Log: func(format string, args ...any) { + msg := fmt.Sprintf(format, args...) + fyne.Do(func() { statusLabel.SetText(msg) }) + }, + SelfAddress: func(addr string) { + fyne.Do(func() { + addrEntry.SetText(addr) + statusLabel.SetText("Connected -- waiting for submissions.") + }) + }, + Submission: func(path, preview string) { + ts := strings.TrimSuffix(filepath.Base(path), ".txt") + oneLine := strings.ReplaceAll(preview, "\n", " ") + if len(oneLine) > 80 { + oneLine = oneLine[:80] + "..." + } + entry := submissionEntry{label: ts + " " + oneLine, path: path} + fyne.Do(func() { + // Newest first: a journalist checking the box wants the + // latest tip on top, not scrolled to the bottom. + submissions = append([]submissionEntry{entry}, submissions...) + list.Refresh() + statusLabel.SetText(fmt.Sprintf("%d submission(s) received.", len(submissions))) + }) + }, + Attachment: func(path string, size int) { + fyne.Do(func() { + statusLabel.SetText(fmt.Sprintf("attachment saved: %s (%d bytes)", path, size)) + }) + }, + Error: func(err error) { + fyne.Do(func() { statusLabel.SetText("error: " + err.Error()) }) + }, + } + + in, err := reader.Start(cfg, hooks) + if err != nil { + statusLabel.SetText("failed to start: " + err.Error()) + } else { + outDirLabel.SetText("Submissions saved to: " + in.OutDir()) + go in.ReceiveLoop() + } + + top := container.NewVBox( + widget.NewLabelWithStyle("NymDrop Reader", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + statusLabel, + widget.NewForm(widget.NewFormItem("Nym inbox address", addrEntry)), + outDirLabel, + ) + + split := container.NewHSplit( + container.NewBorder(widget.NewLabel("Submissions"), nil, nil, nil, list), + container.NewBorder(widget.NewLabel("Content"), nil, nil, nil, content), + ) + split.Offset = 0.35 + + w.SetContent(container.NewBorder(top, nil, nil, nil, split)) + + w.SetCloseIntercept(func() { + if in != nil { + in.Close() + } + w.Close() + }) + + w.ShowAndRun() +} |
