Repos / s4g / dd54544b8e
commit dd54544b8e94264f2e4a7e02bd61b5ede00808fa
Author: Nhân <hi@imnhan.com>
Date:   Mon Jul 17 15:19:17 2023 +0700

    gui dialog to create/open

diff --git a/gui/gui.go b/gui/gui.go
index 93fd10a..cb3ec50 100644
--- a/gui/gui.go
+++ b/gui/gui.go
@@ -2,52 +2,46 @@
 
 import (
 	"bufio"
+	_ "embed"
 	"fmt"
+	"io"
 	"os/exec"
+	"path/filepath"
+	"strings"
 
 	"go.imnhan.com/webmaker2000/gui/ipc"
 )
 
-func Start(tclPath string) {
-	cmd := exec.Command(tclPath, "-encoding", "utf-8")
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		panic(err)
-	}
+//go:embed tcl/main.tcl
+var tclMain []byte
 
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		panic(err)
-	}
+//go:embed tcl/choose-task.tcl
+var tclChooseTask []byte
 
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		panic(err)
-	}
-
-	err = cmd.Start()
-	if err != nil {
-		panic(err)
-	}
+func Main(tclPath string) {
+	interp := newInterp(tclPath)
 
 	go func() {
-		errscanner := bufio.NewScanner(stderr)
+		errscanner := bufio.NewScanner(interp.stderr)
 		for errscanner.Scan() {
 			errtext := errscanner.Text()
 			fmt.Printf("XXX %s\n", errtext)
 		}
 	}()
 
-	fmt.Fprintln(stdin, `source -encoding "utf-8" tcl/main.tcl`)
+	_, err := interp.stdin.Write(tclMain)
+	if err != nil {
+		panic(err)
+	}
 	println("Loaded main tcl script.")
 
-	fmt.Fprintln(stdin, "initialize")
+	fmt.Fprintln(interp.stdin, "initialize")
 
 	respond := func(values ...string) {
-		ipc.Respond(stdin, values)
+		ipc.Respond(interp.stdin, values)
 	}
 
-	for req := range ipc.Requests(stdout) {
+	for req := range ipc.Requests(interp.stdout) {
 		switch req.Method {
 
 		case "forcefocus":
@@ -62,3 +56,73 @@ func Start(tclPath string) {
 
 	println("Tcl process terminated.")
 }
+
+type Task string
+
+const (
+	TaskOpen   Task = "open"
+	TaskCreate Task = "create"
+)
+
+func ChooseTask(tclPath string) (task Task, path string, ok bool) {
+	interp := newInterp(tclPath)
+	interp.stdin.Write(tclChooseTask)
+	interp.stdin.Close()
+	resp, err := io.ReadAll(interp.stdout)
+	if err != nil {
+		panic(err)
+	}
+
+	action, path, found := strings.Cut(strings.TrimSpace(string(resp)), " ")
+	if !found {
+		fmt.Println("No task chosen")
+		return "", "", false
+	}
+
+	switch action {
+	case string(TaskOpen):
+		return TaskOpen, filepath.Dir(path), true
+	case string(TaskCreate):
+		return TaskCreate, path, true
+	default:
+		fmt.Printf("Unexpected tclChooseTask output: %s\n", string(resp))
+		return "", "", false
+	}
+}
+
+type tclInterp struct {
+	cmd    *exec.Cmd
+	stdin  io.WriteCloser
+	stdout io.ReadCloser
+	stderr io.ReadCloser
+}
+
+func newInterp(tclPath string) *tclInterp {
+	cmd := exec.Command(tclPath, "-encoding", "utf-8")
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		panic(err)
+	}
+
+	stdin, err := cmd.StdinPipe()
+	if err != nil {
+		panic(err)
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		panic(err)
+	}
+
+	err = cmd.Start()
+	if err != nil {
+		panic(err)
+	}
+
+	return &tclInterp{
+		cmd:    cmd,
+		stdin:  stdin,
+		stdout: stdout,
+		stderr: stderr,
+	}
+}
diff --git a/gui/tcl/choose-task.tcl b/gui/tcl/choose-task.tcl
new file mode 100644
index 0000000..74f92e2
--- /dev/null
+++ b/gui/tcl/choose-task.tcl
@@ -0,0 +1,41 @@
+package require Tk
+
+wm title . "Create or Open?"
+
+set OS [lindex $tcl_platform(os) 0]
+if {$OS == "Windows"} {
+    ttk::style theme use vista
+} elseif {$OS == "Darwin"} {
+    ttk::style theme use aqua
+} else {
+    ttk::style theme use clam
+}
+
+set types {
+    {{WebMaker2000 Files} {.wbmkr2k}}
+}
+
+ttk::frame .c -padding "10"
+ttk::label .c.label -text {Would you like to create a new blog, or open an existing one?}
+ttk::button .c.createBtn -text "Create..." -padding 5 -command {
+    #set filename [tk_getSaveFile -title "Create" -filetypes $types]
+    set filename [tk_chooseDirectory]
+    if {$filename != ""} {
+        puts "create ${filename}"
+        exit
+    }
+}
+ttk::button .c.openBtn -text "Open..." -padding 5 -command {
+    set filename [tk_getOpenFile -filetypes $types]
+    if {$filename != ""} {
+        puts "open ${filename}"
+        exit
+    }
+}
+
+grid .c -column 0 -row 0
+grid .c.label -column 0 -row 0 -columnspan 2 -pady "0 10"
+grid .c.createBtn -column 0 -row 1 -padx 10
+grid .c.openBtn -column 1 -row 1 -padx 10
+
+tk::PlaceWindow . center
diff --git a/gui/tcl/main.tcl b/gui/tcl/main.tcl
index 0893a67..7dbf5ea 100644
--- a/gui/tcl/main.tcl
+++ b/gui/tcl/main.tcl
@@ -10,183 +10,23 @@ foreach p {stdin stdout stderr} {
 
 package require Tk
 
-wm title . "WebMaker200"
+wm title . "WebMaker2000"
 tk appname webmaker2000
 
-# Proper Windows theme doesn't allow setting fieldbackground on text inputs,
-# so let's settle with `clam` instead.
-ttk::style theme use clam
+set OS [lindex $tcl_platform(os) 0]
+if {$OS == "Windows"} {
+    ttk::style theme use vista
+} elseif {$OS == "Darwin"} {
+    ttk::style theme use aqua
+} else {
+    ttk::style theme use clam
+}
 
 wm protocol . WM_DELETE_WINDOW {
     exit 0
 }
 
-# Data that we send to the actual web-based overlay:
-array set scoreboard {
-    description ""
-    subtitle ""
-    p1name ""
-    p1country ""
-    p1score 0
-    p1team ""
-    p2name ""
-    p2country ""
-    p2score 0
-    p2team ""
-}
-
-# $applied_scoreboard represents data that has actually been applied
-# to the overlay. This is used to display diff in the UI, and to restore data
-# when user clicks "Discard".
-foreach key [array names scoreboard] {
-    set applied_scoreboard($key) scoreboard($key)
-}
-
-array set var_to_widget {
-    description .n.m.description.entry
-    subtitle .n.m.subtitle.entry
-    p1name .n.m.players.p1name
-    p1country .n.m.players.p1country
-    p1score .n.m.players.p1score
-    p1team .n.m.players.p1team
-    p2name .n.m.players.p2name
-    p2country .n.m.players.p2country
-    p2score .n.m.players.p2score
-    p2team .n.m.players.p2team
-}
-
-array set startgg {
-    token ""
-    slug ""
-    msg ""
-}
-
-# GUI has 2 tabs: Main (.n.m) and start.gg (.n.s)
-
-ttk::notebook .n
-ttk::frame .n.m -padding 5
-ttk::frame .n.s -padding 5
-.n add .n.m -text Main
-.n add .n.s -text start.gg
-grid .n -column 0 -row 0 -sticky NESW
-
-# Main tab:
-
-ttk::frame .n.m.description
-ttk::label .n.m.description.lbl -text "Title"
-ttk::entry .n.m.description.entry -textvariable scoreboard(description)
-ttk::frame .n.m.subtitle
-ttk::label .n.m.subtitle.lbl -text "Subtitle"
-ttk::entry .n.m.subtitle.entry -textvariable scoreboard(subtitle)
-ttk::frame .n.m.players
-ttk::label .n.m.players.p1lbl -text "Player 1"
-ttk::combobox .n.m.players.p1name -textvariable scoreboard(p1name) -width 35
-ttk::combobox .n.m.players.p1country -textvariable scoreboard(p1country) -width 5
-ttk::spinbox .n.m.players.p1score -textvariable scoreboard(p1score) -from 0 -to 999 -width 4
-ttk::button .n.m.players.p1win -text "▲ Win" -width 6 -command {incr scoreboard(p1score)}
-ttk::label .n.m.players.p1teamlbl -text "Team 1"
-ttk::combobox .n.m.players.p1team -textvariable scoreboard(p1team)
-ttk::separator .n.m.players.separator -orient horizontal
-ttk::label .n.m.players.p2lbl -text "Player 2"
-ttk::combobox .n.m.players.p2name -textvariable scoreboard(p2name) -width 35
-ttk::combobox .n.m.players.p2country -textvariable scoreboard(p2country) -width 5
-ttk::spinbox .n.m.players.p2score -textvariable scoreboard(p2score) -from 0 -to 999 -width 4
-ttk::button .n.m.players.p2win -text "▲ Win" -width 6 -command {incr scoreboard(p2score)}
-ttk::label .n.m.players.p2teamlbl -text "Team 2"
-ttk::combobox .n.m.players.p2team -textvariable scoreboard(p2team)
-ttk::frame .n.m.buttons
-ttk::button .n.m.buttons.apply -text "▶ Apply" -command applyscoreboard
-ttk::button .n.m.buttons.discard -text "✖ Discard" -command discardscoreboard
-ttk::button .n.m.buttons.reset -text "↶ Reset scores" -command {
-    set scoreboard(p1score) 0
-    set scoreboard(p2score) 0
-}
-ttk::button .n.m.buttons.swap -text "⇄ Swap players" -command {
-    # Since country is updated whenever name is updated, we'll need to write
-    # countries last.
-    set p1country $scoreboard(p1country)
-    set p2country $scoreboard(p2country)
-    foreach key {name score team} {
-        set tmp $scoreboard(p1$key)
-        set scoreboard(p1$key) $scoreboard(p2$key)
-        set scoreboard(p2$key) $tmp
-    }
-    set scoreboard(p1country) $p2country
-    set scoreboard(p2country) $p1country
-}
-ttk::label .n.m.status -textvariable mainstatus
-grid .n.m.description -row 0 -column 0 -sticky NESW -pady {0 5}
-grid .n.m.description.lbl -row 0 -column 0 -padx {0 5}
-grid .n.m.description.entry -row 0 -column 1 -sticky EW
-grid columnconfigure .n.m.description 1 -weight 1
-grid .n.m.subtitle -row 1 -column 0 -sticky NESW -pady {0 5}
-grid .n.m.subtitle.lbl -row 0 -column 0 -padx {0 5}
-grid .n.m.subtitle.entry -row 0 -column 1 -sticky EW
-grid columnconfigure .n.m.subtitle 1 -weight 1
-grid .n.m.players -row 2 -column 0
-grid .n.m.players.p1lbl -row 0 -column 0
-grid .n.m.players.p1name -row 0 -column 1
-grid .n.m.players.p1country -row 0 -column 2
-grid .n.m.players.p1score -row 0 -column 3
-grid .n.m.players.p1win -row 0 -column 4 -padx {5 0} -rowspan 2 -sticky NS
-grid .n.m.players.p1teamlbl -row 1 -column 0
-grid .n.m.players.p1team -row 1 -column 1 -columnspan 3 -sticky EW
-grid .n.m.players.separator -row 2 -column 0 -columnspan 5 -pady 10 -sticky EW
-grid .n.m.players.p2lbl -row 3 -column 0
-grid .n.m.players.p2name -row 3 -column 1
-grid .n.m.players.p2country -row 3 -column 2
-grid .n.m.players.p2score -row 3 -column 3
-grid .n.m.players.p2win -row 3 -column 4 -padx {5 0} -rowspan 2 -sticky NS
-grid .n.m.players.p2teamlbl -row 4 -column 0
-grid .n.m.players.p2team -row 4 -column 1 -columnspan 3 -sticky EW
-grid .n.m.buttons -row 3 -column 0 -sticky W -pady {10 0}
-grid .n.m.buttons.apply -row 0 -column 0
-grid .n.m.buttons.discard -row 0 -column 1
-grid .n.m.buttons.reset -row 0 -column 2
-grid .n.m.buttons.swap -row 0 -column 3
-grid .n.m.status -row 4 -column 0 -columnspan 5 -pady {10 0} -sticky EW
-grid columnconfigure .n.m.players 2 -pad 5
-grid columnconfigure .n.m.buttons 1 -pad 15
-grid columnconfigure .n.m.buttons 3 -pad 15
-grid rowconfigure .n.m.players 1 -pad 5
-grid rowconfigure .n.m.players 3 -pad 5
-
-# start.gg tab:
-
-#.n select .n.s; # for debug only
-ttk::label .n.s.tokenlbl -text "Personal token: "
-ttk::entry .n.s.token -show * -textvariable startgg(token)
-ttk::label .n.s.tournamentlbl -text "Tournament slug: "
-ttk::entry .n.s.tournamentslug -textvariable startgg(slug)
-ttk::frame .n.s.buttons
-ttk::button .n.s.buttons.fetch -text "↓ Fetch players" -command fetchplayers
-ttk::button .n.s.buttons.clear -text "✘ Clear" -command clearstartgg
-ttk::label .n.s.msg -textvariable startgg(msg)
-
-grid .n.s.tokenlbl -row 0 -column 0 -sticky W
-grid .n.s.token -row 0 -column 1 -sticky EW
-grid .n.s.tournamentlbl -row 1 -column 0 -sticky W
-grid .n.s.tournamentslug -row 1 -column 1 -sticky EW
-grid .n.s.buttons -row 2 -column 1 -stick WE
-grid .n.s.buttons.fetch -stick W
-grid .n.s.buttons.clear -row 0 -column 1 -stick W -padx 5
-grid .n.s.msg -row 3 -column 1 -stick W
-grid columnconfigure .n.s 1 -weight 1
-grid rowconfigure .n.s 1 -pad 5
-grid rowconfigure .n.s 2 -pad 5
-
 proc initialize {} {
-    loadicon
-    loadstartgg
-    loadwebmsg
-    loadcountrycodes
-    loadscoreboard
-    loadplayernames
-
-    setupdiffcheck
-    setupplayersuggestion
-
-
     # By default this window is not focused and not even brought to
     # foreground on Windows. I suspect it's because tcl is exec'ed from Go.
     # The old "iconify, deiconify" trick no longer seems to work, so this time
diff --git a/main.go b/main.go
index 4849340..1de88ce 100644
--- a/main.go
+++ b/main.go
@@ -18,6 +18,7 @@
 	"time"
 
 	"go.imnhan.com/webmaker2000/djot"
+	"go.imnhan.com/webmaker2000/gui"
 	"go.imnhan.com/webmaker2000/livereload"
 	"go.imnhan.com/webmaker2000/writablefs"
 )
@@ -37,7 +38,7 @@ func main() {
 	var cmd string
 	var args []string
 	if len(os.Args) < 2 {
-		cmd = "serve"
+		cmd = "gui"
 		args = os.Args[1:]
 	} else {
 		cmd = os.Args[1]
@@ -60,6 +61,8 @@ func main() {
 	case "serve":
 		serveCmd.Parse(args)
 		handleServeCmd(serveFolder, servePort)
+	case "gui":
+		handleGuiCmd()
 	default:
 		invalidCommand()
 	}
@@ -73,6 +76,22 @@ func handleNewCmd(folder string) {
 	}
 }
 
+func handleGuiCmd() {
+	task, path, ok := gui.ChooseTask(gui.DefaultTclPath)
+	if !ok {
+		return
+	}
+
+	switch task {
+	case gui.TaskOpen:
+		fmt.Println(task, path)
+	case gui.TaskCreate:
+		fmt.Println(task, path)
+	default:
+		panic(task)
+	}
+}
+
 func handleServeCmd(folder, port string) {
 	djot.StartService()
 	fmt.Println("Started djot.js service")