Compare commits
2 commits
9656db5335
...
d4d14aa94a
Author | SHA1 | Date | |
---|---|---|---|
d4d14aa94a | |||
8a0ef32c91 |
14 changed files with 269 additions and 187 deletions
92
cmd/client/app.go
Normal file
92
cmd/client/app.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
|
||||||
|
"git.smsvc.net/pomodoro/ChronoTomato/internal/helper"
|
||||||
|
"git.smsvc.net/pomodoro/ChronoTomato/internal/websocket"
|
||||||
|
ChronoTomato "git.smsvc.net/pomodoro/ChronoTomato/pkg/models"
|
||||||
|
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/help"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
config ChronoTomato.Config
|
||||||
|
client websocket.Client
|
||||||
|
)
|
||||||
|
|
||||||
|
type app struct {
|
||||||
|
keys keyMap
|
||||||
|
help help.Model
|
||||||
|
channel chan GoTomato.ServerMessage
|
||||||
|
pomodoro GoTomato.ServerMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func myApp() app {
|
||||||
|
return app{
|
||||||
|
keys: keys,
|
||||||
|
help: help.New(),
|
||||||
|
channel: make(chan GoTomato.ServerMessage),
|
||||||
|
pomodoro: GoTomato.ServerMessage{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for channel signal and return the ServerMessage as a tea.Msg.
|
||||||
|
// Return tea.Quit() if the channel has been closed.
|
||||||
|
// Excapsulate all this in a tea.Cmd() because bubbletea demands it.
|
||||||
|
func (a app) waitForChannelSignal() tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
content, open := <-a.channel
|
||||||
|
if !open {
|
||||||
|
return tea.Quit()
|
||||||
|
}
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a app) Init() tea.Cmd {
|
||||||
|
client = websocket.Connect(config.URL)
|
||||||
|
client.Password = config.Password
|
||||||
|
go client.ProcessServerMessages(a.channel)
|
||||||
|
|
||||||
|
return tea.Batch(
|
||||||
|
tea.ClearScreen,
|
||||||
|
a.waitForChannelSignal(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() {
|
||||||
|
var (
|
||||||
|
defaultConfigFile = "~/.config/ChronoTomato.yml"
|
||||||
|
|
||||||
|
parameter_url = flag.String("url", "", "GoTomato Server URL (eg ws://localhost:8080/ws)")
|
||||||
|
parameter_password = flag.String("password", "", "Control password for pomodoro session")
|
||||||
|
configfile = flag.String("config", defaultConfigFile, "Path to config file")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// read passed config file or try to use default config
|
||||||
|
if *configfile != defaultConfigFile {
|
||||||
|
config = helper.ParseConfig(*configfile)
|
||||||
|
} else {
|
||||||
|
if helper.FileExists(*configfile) {
|
||||||
|
config = helper.ParseConfig(*configfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cli parameters always supersede config file
|
||||||
|
if *parameter_url != "" {
|
||||||
|
config.URL = *parameter_url
|
||||||
|
}
|
||||||
|
if *parameter_password != "" {
|
||||||
|
config.Password = *parameter_password
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := tea.NewProgram(myApp()).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Could not start program:", "msg", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package frontend
|
package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send desktop notifications on the start of each segment
|
// Send desktop notifications on the start of each segment
|
||||||
func desktopNotifications(pomodoro GoTomato.ServerMessage) {
|
func DesktopNotifications(pomodoro GoTomato.ServerMessage) {
|
||||||
var (
|
var (
|
||||||
duration int
|
duration int
|
||||||
notification string
|
notification string
|
|
@ -1,25 +1,24 @@
|
||||||
package frontend
|
package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fatih/color"
|
|
||||||
"strings"
|
"github.com/alecthomas/colour"
|
||||||
|
|
||||||
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update the terminal output based on the passed ServerMessage
|
// Return terminal output based on the passed ServerMessage
|
||||||
func terminalOutput(pomodoro GoTomato.ServerMessage) {
|
func TerminalOutput(pomodoro GoTomato.ServerMessage) string {
|
||||||
var (
|
var (
|
||||||
|
output string
|
||||||
|
|
||||||
modePrefix string
|
modePrefix string
|
||||||
timerOutput string
|
timerOutput string
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clear the screen
|
|
||||||
fmt.Print("\033[H\033[2J")
|
|
||||||
|
|
||||||
// header
|
// header
|
||||||
color.Blue("Work: %d ◊ Break: %d ◊ Longbreak: %d\n\n",
|
output += colour.Sprintf("^D^4Work: %d ◊ Break: %d ◊ Longbreak: %d^R\n\n",
|
||||||
pomodoro.Settings.Work/60,
|
pomodoro.Settings.Work/60,
|
||||||
pomodoro.Settings.ShortBreak/60,
|
pomodoro.Settings.ShortBreak/60,
|
||||||
pomodoro.Settings.LongBreak/60,
|
pomodoro.Settings.LongBreak/60,
|
||||||
|
@ -41,14 +40,12 @@ func terminalOutput(pomodoro GoTomato.ServerMessage) {
|
||||||
timerOutput = fmt.Sprintf("⏳ %02d:%02d", minutes, seconds)
|
timerOutput = fmt.Sprintf("⏳ %02d:%02d", minutes, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Session: %d/%d\n",
|
output += fmt.Sprintf("Session: %d/%d\n",
|
||||||
pomodoro.Session,
|
pomodoro.Session,
|
||||||
pomodoro.Settings.Sessions,
|
pomodoro.Settings.Sessions,
|
||||||
)
|
)
|
||||||
fmt.Printf("%s %s\n", modePrefix, pomodoro.Mode)
|
output += fmt.Sprintf("%s %s\n", modePrefix, pomodoro.Mode)
|
||||||
fmt.Printf(timerOutput)
|
output += fmt.Sprintf(timerOutput)
|
||||||
|
|
||||||
//footer
|
return output
|
||||||
fmt.Printf(strings.Repeat("\n", 3))
|
|
||||||
color.White("space: start/pause/resume • s: stop • r: reset pomodoro • q: quit")
|
|
||||||
}
|
}
|
44
cmd/client/keys.go
Normal file
44
cmd/client/keys.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "github.com/charmbracelet/bubbles/key"
|
||||||
|
|
||||||
|
// keyMap defines a set of keybindings. To work for help it must satisfy
|
||||||
|
// key.Map. It could also very easily be a map[string]key.Binding.
|
||||||
|
type keyMap struct {
|
||||||
|
Start key.Binding
|
||||||
|
Stop key.Binding
|
||||||
|
Reset key.Binding
|
||||||
|
Quit key.Binding
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = keyMap{
|
||||||
|
Start: key.NewBinding(
|
||||||
|
key.WithKeys(" "),
|
||||||
|
key.WithHelp("space", "start/pause/resume"),
|
||||||
|
),
|
||||||
|
Stop: key.NewBinding(
|
||||||
|
key.WithKeys("s"),
|
||||||
|
key.WithHelp("s", "stop"),
|
||||||
|
),
|
||||||
|
Reset: key.NewBinding(
|
||||||
|
key.WithKeys("r"),
|
||||||
|
key.WithHelp("r", "reset"),
|
||||||
|
),
|
||||||
|
Quit: key.NewBinding(
|
||||||
|
key.WithKeys("q"),
|
||||||
|
key.WithHelp("q", "quit"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyMap) ShortHelp() []key.Binding {
|
||||||
|
return []key.Binding{
|
||||||
|
keys.Start,
|
||||||
|
keys.Stop,
|
||||||
|
keys.Reset,
|
||||||
|
keys.Quit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyMap) FullHelp() [][]key.Binding {
|
||||||
|
return [][]key.Binding{}
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"atomicgo.dev/cursor"
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"git.smsvc.net/pomodoro/ChronoTomato/internal/frontend"
|
|
||||||
"git.smsvc.net/pomodoro/ChronoTomato/internal/helper"
|
|
||||||
"git.smsvc.net/pomodoro/ChronoTomato/internal/websocket"
|
|
||||||
|
|
||||||
ChronoTomato "git.smsvc.net/pomodoro/ChronoTomato/pkg/models"
|
|
||||||
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
config ChronoTomato.Config
|
|
||||||
|
|
||||||
defaultConfigFile = "~/.config/ChronoTomato.yml"
|
|
||||||
|
|
||||||
parameter_url = flag.String("url", "", "GoTomato Server URL (eg ws://localhost:8080/ws)")
|
|
||||||
parameter_password = flag.String("password", "", "Control password for pomodoro session")
|
|
||||||
configfile = flag.String("config", "", "Path to config file")
|
|
||||||
)
|
|
||||||
|
|
||||||
func Start() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// show/hide cursor for nicer interface
|
|
||||||
cursor.Hide()
|
|
||||||
defer cursor.Show()
|
|
||||||
|
|
||||||
// read passed config file or try to use default config
|
|
||||||
if *configfile != "" {
|
|
||||||
config = helper.ParseConfig(*configfile)
|
|
||||||
} else {
|
|
||||||
if helper.FileExists(defaultConfigFile) {
|
|
||||||
config = helper.ParseConfig(defaultConfigFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cli parameters always supersede config file
|
|
||||||
if *parameter_url != "" {
|
|
||||||
config.URL = *parameter_url
|
|
||||||
}
|
|
||||||
if *parameter_password != "" {
|
|
||||||
config.Password = *parameter_password
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a client
|
|
||||||
client := websocket.Connect(config.URL)
|
|
||||||
client.Password = config.Password
|
|
||||||
|
|
||||||
// Receive server messages und update the terminal
|
|
||||||
channel := make(chan GoTomato.ServerMessage)
|
|
||||||
go client.ProcessServerMessages(channel)
|
|
||||||
frontend.UpdateLoop(client, config, channel)
|
|
||||||
|
|
||||||
// disconnect from server
|
|
||||||
client.Disconnect()
|
|
||||||
}
|
|
45
cmd/client/update.go
Normal file
45
cmd/client/update.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sends start/pause/resume based on the state of the pomodoro
|
||||||
|
func start_pause_resume(message GoTomato.ServerMessage) string {
|
||||||
|
if !message.Ongoing {
|
||||||
|
return "start"
|
||||||
|
}
|
||||||
|
if message.Paused {
|
||||||
|
return "resume"
|
||||||
|
} else {
|
||||||
|
return "pause"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a app) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case GoTomato.ServerMessage:
|
||||||
|
a.pomodoro = msg
|
||||||
|
return a, a.waitForChannelSignal()
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch {
|
||||||
|
case key.Matches(msg, a.keys.Start):
|
||||||
|
cmd := start_pause_resume(a.pomodoro)
|
||||||
|
client.SendCmd(cmd)
|
||||||
|
case key.Matches(msg, a.keys.Stop):
|
||||||
|
client.SendCmd("stop")
|
||||||
|
case key.Matches(msg, a.keys.Reset):
|
||||||
|
if config.PomodoroConfig != (GoTomato.PomodoroConfig{}) {
|
||||||
|
client.SendSettingsUpdate(config.PomodoroConfig)
|
||||||
|
}
|
||||||
|
case key.Matches(msg, a.keys.Quit):
|
||||||
|
client.Disconnect()
|
||||||
|
return a, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, nil
|
||||||
|
}
|
24
cmd/client/view.go
Normal file
24
cmd/client/view.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.smsvc.net/pomodoro/ChronoTomato/cmd/client/helper"
|
||||||
|
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a app) View() string {
|
||||||
|
var body string
|
||||||
|
|
||||||
|
if a.pomodoro != (GoTomato.ServerMessage{}) {
|
||||||
|
body = helper.TerminalOutput(a.pomodoro)
|
||||||
|
helper.DesktopNotifications(a.pomodoro)
|
||||||
|
} else {
|
||||||
|
body = "Waiting for first server message..."
|
||||||
|
}
|
||||||
|
|
||||||
|
helpView := a.help.View(a.keys)
|
||||||
|
height := 8 - strings.Count(body, "\n") - strings.Count(helpView, "\n")
|
||||||
|
|
||||||
|
return body + strings.Repeat("\n", height) + helpView
|
||||||
|
}
|
22
go.mod
22
go.mod
|
@ -3,11 +3,11 @@ module git.smsvc.net/pomodoro/ChronoTomato
|
||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
atomicgo.dev/cursor v0.2.0
|
|
||||||
git.smsvc.net/pomodoro/GoTomato v0.0.4
|
git.smsvc.net/pomodoro/GoTomato v0.0.4
|
||||||
|
github.com/alecthomas/colour v0.1.0
|
||||||
|
github.com/charmbracelet/bubbles v0.20.0
|
||||||
|
github.com/charmbracelet/bubbletea v1.1.2
|
||||||
github.com/charmbracelet/log v0.4.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
|
||||||
github.com/fatih/color v1.18.0
|
|
||||||
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4
|
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
@ -15,21 +15,27 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/lipgloss v0.10.0 // indirect
|
github.com/charmbracelet/lipgloss v0.13.0 // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.0 // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
|
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
|
golang.org/x/text v0.3.8 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
)
|
)
|
||||||
|
|
49
go.sum
49
go.sum
|
@ -1,19 +1,25 @@
|
||||||
atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw=
|
|
||||||
atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
|
|
||||||
git.smsvc.net/pomodoro/GoTomato v0.0.4 h1:+pCbPXUMtPteQilMe3QNo6fl67kB9gO6C/g0sNj1zck=
|
git.smsvc.net/pomodoro/GoTomato v0.0.4 h1:+pCbPXUMtPteQilMe3QNo6fl67kB9gO6C/g0sNj1zck=
|
||||||
git.smsvc.net/pomodoro/GoTomato v0.0.4/go.mod h1:rNFUjjBMKplygWYbgErWd4cD8JQ66h0KyiK54cGktJo=
|
git.smsvc.net/pomodoro/GoTomato v0.0.4/go.mod h1:rNFUjjBMKplygWYbgErWd4cD8JQ66h0KyiK54cGktJo=
|
||||||
|
github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
|
||||||
|
github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
|
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||||
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
|
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||||
|
github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc=
|
||||||
|
github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E=
|
||||||
|
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||||
|
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.0 h1:NqwHA4B23VwsDn4H3VcNX1W1tOmgnvY1NDx5tOXdnOU=
|
||||||
|
github.com/charmbracelet/x/ansi v0.4.0/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
|
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
||||||
|
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
|
||||||
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 h1:ygs9POGDQpQGLJPlq4+0LBUmMBNox1N4JSpw+OETcvI=
|
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4 h1:ygs9POGDQpQGLJPlq4+0LBUmMBNox1N4JSpw+OETcvI=
|
||||||
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4=
|
github.com/gen2brain/beeep v0.0.0-20240516210008-9c006672e7f4/go.mod h1:0W7dI87PvXJ1Sjs0QPvWXKcQmNERY77e8l7GFhZB/s4=
|
||||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
|
@ -31,23 +37,22 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
@ -57,10 +62,14 @@ github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG0
|
||||||
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
|
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/eiannone/keyboard"
|
|
||||||
|
|
||||||
"git.smsvc.net/pomodoro/ChronoTomato/internal/websocket"
|
|
||||||
|
|
||||||
ChronoTomato "git.smsvc.net/pomodoro/ChronoTomato/pkg/models"
|
|
||||||
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sends start/pause/resume based on the state of the pomodoro
|
|
||||||
func start_pause_resume(message GoTomato.ServerMessage) string {
|
|
||||||
if !message.Ongoing {
|
|
||||||
return "start"
|
|
||||||
}
|
|
||||||
if message.Paused {
|
|
||||||
return "resume"
|
|
||||||
} else {
|
|
||||||
return "pause"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reads a KeyEvent and sends the matching command
|
|
||||||
func menuHandler(key keyboard.KeyEvent, client websocket.Client, config ChronoTomato.Config, message GoTomato.ServerMessage) bool {
|
|
||||||
switch key.Rune {
|
|
||||||
case 0: // space
|
|
||||||
cmd := start_pause_resume(message)
|
|
||||||
client.SendCmd(cmd)
|
|
||||||
case 115: // s
|
|
||||||
client.SendCmd("stop")
|
|
||||||
case 114: // r
|
|
||||||
if config.PomodoroConfig != (GoTomato.PomodoroConfig{}) {
|
|
||||||
client.SendSettingsUpdate(config.PomodoroConfig)
|
|
||||||
}
|
|
||||||
case 113: // q
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package frontend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/eiannone/keyboard"
|
|
||||||
|
|
||||||
"git.smsvc.net/pomodoro/ChronoTomato/internal/websocket"
|
|
||||||
|
|
||||||
ChronoTomato "git.smsvc.net/pomodoro/ChronoTomato/pkg/models"
|
|
||||||
GoTomato "git.smsvc.net/pomodoro/GoTomato/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update the terminal and send desktop notifications until the websocket if closed or "quit"
|
|
||||||
func UpdateLoop(client websocket.Client, config ChronoTomato.Config, channel <-chan GoTomato.ServerMessage) {
|
|
||||||
var message GoTomato.ServerMessage
|
|
||||||
|
|
||||||
// listen for key events
|
|
||||||
keysEvents, _ := keyboard.GetKeys(1)
|
|
||||||
defer keyboard.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case message = <-channel:
|
|
||||||
// for every received message
|
|
||||||
desktopNotifications(message)
|
|
||||||
terminalOutput(message)
|
|
||||||
case keypress := <-keysEvents:
|
|
||||||
// react to key pressed
|
|
||||||
if !menuHandler(keypress, client, config, message) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-websocket.Done:
|
|
||||||
// connection closed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
ChronoTomato "git.smsvc.net/pomodoro/ChronoTomato/pkg/models"
|
ChronoTomato "git.smsvc.net/pomodoro/ChronoTomato/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Expands the `~` in a passed filename
|
// Expands the "~" in a passed filename
|
||||||
func expandUnixPath(filename string) string {
|
func expandUnixPath(filename string) string {
|
||||||
if strings.HasPrefix(filename, "~/") {
|
if strings.HasPrefix(filename, "~/") {
|
||||||
dirname, _ := os.UserHomeDir()
|
dirname, _ := os.UserHomeDir()
|
||||||
|
|
|
@ -12,13 +12,14 @@ type Client ChronoTomato.GoTomatoClient // New websocket client
|
||||||
|
|
||||||
// Connects to websocket
|
// Connects to websocket
|
||||||
func Connect(url string) Client {
|
func Connect(url string) Client {
|
||||||
log.Info("Connected ", "host", url)
|
|
||||||
|
|
||||||
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Dial error!", "reason", err)
|
log.Fatal("Dial error!", "reason", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Connected ", "host", url)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
return Client{Conn: conn}
|
return Client{Conn: conn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
@ -11,7 +10,8 @@ import (
|
||||||
|
|
||||||
var Done = make(chan struct{})
|
var Done = make(chan struct{})
|
||||||
|
|
||||||
// Receives websocket messages and write them to `channel`
|
// Receives websocket messages and writes them to a channel.
|
||||||
|
// Closes the channel if websocket closes.
|
||||||
func (c Client) ProcessServerMessages(channel chan<- GoTomato.ServerMessage) {
|
func (c Client) ProcessServerMessages(channel chan<- GoTomato.ServerMessage) {
|
||||||
var serverMessage GoTomato.ServerMessage
|
var serverMessage GoTomato.ServerMessage
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ func (c Client) ProcessServerMessages(channel chan<- GoTomato.ServerMessage) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Log any other errors
|
// Log any other errors
|
||||||
fmt.Println()
|
|
||||||
log.Error("Read error!", "reason", err)
|
log.Error("Read error!", "reason", err)
|
||||||
|
close(channel)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue