summaryrefslogtreecommitdiffstats
path: root/cmd/nymdrop-reader/gui.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/nymdrop-reader/gui.go')
-rw-r--r--cmd/nymdrop-reader/gui.go135
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()
+}