Compare commits
2 commits
5c08a9b184
...
f9b2a76894
Author | SHA1 | Date | |
---|---|---|---|
f9b2a76894 | |||
314e71acba |
7 changed files with 65 additions and 42 deletions
24
README.md
24
README.md
|
@ -32,6 +32,22 @@ Clients communicate with the server by sending JSON-encoded commands. Each comma
|
||||||
| `resume` | Resumes a paused session | `{"command": "resume"}` |
|
| `resume` | Resumes a paused session | `{"command": "resume"}` |
|
||||||
| `reset` | Stops and resets the current session| `{"command": "reset"}` |
|
| `reset` | Stops and resets the current session| `{"command": "reset"}` |
|
||||||
|
|
||||||
|
#### Optional Start Parameters
|
||||||
|
The Start-Command may contain an optional Pomodoro-Config, which allows you to customize the length of the work session, short break, long break, and the number of sessions. If no configuration is provided, the server will use default values.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
command: "start",
|
||||||
|
config: {
|
||||||
|
"work": 10, // Length of the work session in seconds
|
||||||
|
"shortBreak": 5, // Length of the short break in seconds
|
||||||
|
"longBreak": 10, // Length of the long break in seconds
|
||||||
|
"sessions": 2 // Number of total sessions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Server Messages
|
### Server Messages
|
||||||
|
|
||||||
The server sends JSON-encoded messages to all connected clients to update them on the current state of the Pomodoro session.
|
The server sends JSON-encoded messages to all connected clients to update them on the current state of the Pomodoro session.
|
||||||
|
@ -39,14 +55,14 @@ These messages contain the following fields:
|
||||||
|
|
||||||
- mode: Indicates the current phase of the Pomodoro session ("Work", "ShortBreak", "LongBreak", or "none" if no session is running).
|
- mode: Indicates the current phase of the Pomodoro session ("Work", "ShortBreak", "LongBreak", or "none" if no session is running).
|
||||||
- session: The current session number (e.g., 1 for the first work session).
|
- session: The current session number (e.g., 1 for the first work session).
|
||||||
- max_session: The total number of sessions for the current Pomodoro cycle (e.g., 4 if the cycle consists of 4 work/break sessions).
|
- total_sessions: The total number of sessions for the current Pomodoro cycle (e.g., 4 if the cycle consists of 4 work/break sessions).
|
||||||
- time_left: The remaining time for the current mode, in seconds (e.g., 900 for 15 minutes).
|
- time_left: The remaining time for the current mode, in seconds (e.g., 900 for 15 minutes).
|
||||||
|
|
||||||
| Message Type | Sent by Server | Example |
|
| Message Type | Sent by Server | Example |
|
||||||
| --------------------- | ------------------------------------------------ | --------------------------------------------------- |
|
| --------------------- | ------------------------------------------------ | --------------------------------------------------- |
|
||||||
| Welcome Message | Upon initial client connection | `{"mode": "none", "session": 0, "max_session": 0, "time_left": 0}` |
|
| Welcome Message | Upon initial client connection | `{"mode": "none", "session": 0, "total_sessions": 0, "time_left": 0}` |
|
||||||
| Session Update | Periodically during a session | `{"mode": "Work", "session": 1, "max_session": 4, "time_left": 900}` |
|
| Session Update | Periodically during a session | `{"mode": "Work", "session": 1, "total_sessions": 4, "time_left": 900}` |
|
||||||
| Session End/Reset | After session completes or is reset | `{"mode": "none", "session": 0, "max_session": 0, "time_left": 0}` |
|
| Session End/Reset | After session completes or is reset | `{"mode": "none", "session": 0, "total_sessions": 0, "time_left": 0}` |
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|
|
@ -41,11 +41,11 @@
|
||||||
var data = JSON.parse(event.data);
|
var data = JSON.parse(event.data);
|
||||||
var mode = data.mode;
|
var mode = data.mode;
|
||||||
var session = data.session;
|
var session = data.session;
|
||||||
var maxSession = data.max_session;
|
var totalSession = data.total_sessions;
|
||||||
var timeLeft = data.time_left;
|
var timeLeft = data.time_left;
|
||||||
|
|
||||||
document.getElementById("timer").innerText =
|
document.getElementById("timer").innerText =
|
||||||
mode + " Session " + session + "/" + maxSession + ": " + formatTime(timeLeft);
|
mode + " Session " + session + "/" + totalSession + ": " + formatTime(timeLeft);
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = function () {
|
ws.onclose = function () {
|
||||||
|
@ -61,7 +61,8 @@
|
||||||
|
|
||||||
// Start Button Click Event
|
// Start Button Click Event
|
||||||
document.getElementById("startButton").addEventListener("click", function () {
|
document.getElementById("startButton").addEventListener("click", function () {
|
||||||
ws.send(JSON.stringify({command: "start"}));
|
// ws.send(JSON.stringify({command: "start"}));
|
||||||
|
ws.send(JSON.stringify({command: "start", config: {work: 10, shortBreak: 5, longBreak: 10, sessions: 2}}));
|
||||||
|
|
||||||
// Hide start button and show pause/resume and stop buttons
|
// Hide start button and show pause/resume and stop buttons
|
||||||
document.getElementById("startButton").style.display = "none";
|
document.getElementById("startButton").style.display = "none";
|
||||||
|
|
|
@ -7,13 +7,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var PomodoroConfig = models.GoTomatoPomodoroConfig{
|
|
||||||
Work: 15 * 60,
|
|
||||||
ShortBreak: 5 * 60,
|
|
||||||
LongBreak: 10 * 60,
|
|
||||||
Sessions: 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
var pomodoroOngoing bool
|
var pomodoroOngoing bool
|
||||||
var pomodoroPaused bool
|
var pomodoroPaused bool
|
||||||
|
|
||||||
|
@ -24,22 +17,22 @@ var pomodoroResumeChannel = make(chan bool, 1)
|
||||||
var mu sync.Mutex // to synchronize access to shared state
|
var mu sync.Mutex // to synchronize access to shared state
|
||||||
|
|
||||||
// RunPomodoro iterates the Pomodoro work/break sessions.
|
// RunPomodoro iterates the Pomodoro work/break sessions.
|
||||||
func RunPomodoro(clients map[*websocket.Conn]*models.Client) {
|
func RunPomodoro(clients map[*websocket.Conn]*models.Client, config models.GoTomatoPomodoroConfig) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
pomodoroOngoing = true
|
pomodoroOngoing = true
|
||||||
pomodoroPaused = false
|
pomodoroPaused = false
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
|
|
||||||
for session := 1; session <= PomodoroConfig.Sessions; session++ {
|
for session := 1; session <= config.Sessions; session++ {
|
||||||
if !startTimer(clients, PomodoroConfig.Work, "Work", session) {
|
if !startTimer(clients, config.Work, "Work", session, config.Sessions) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if session == PomodoroConfig.Sessions {
|
if session == config.Sessions {
|
||||||
if !startTimer(clients, PomodoroConfig.LongBreak, "LongBreak", session) {
|
if !startTimer(clients, config.LongBreak, "LongBreak", session, config.Sessions) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !startTimer(clients, PomodoroConfig.ShortBreak, "ShortBreak", session) {
|
if !startTimer(clients, config.ShortBreak, "ShortBreak", session, config.Sessions) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,10 +52,10 @@ func ResetPomodoro(clients map[*websocket.Conn]*models.Client) {
|
||||||
|
|
||||||
// Broadcast the reset message to all clients
|
// Broadcast the reset message to all clients
|
||||||
broadcast.BroadcastMessage(clients, models.ServerMessage{
|
broadcast.BroadcastMessage(clients, models.ServerMessage{
|
||||||
Mode: "none",
|
Mode: "none",
|
||||||
Session: 0,
|
Session: 0,
|
||||||
MaxSession: 0,
|
TotalSession: 0,
|
||||||
TimeLeft: 0,
|
TimeLeft: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send a reset signal to stop any running timers
|
// Send a reset signal to stop any running timers
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// startTimer runs the countdown and broadcasts every second.
|
// startTimer runs the countdown and broadcasts every second.
|
||||||
func startTimer(clients map[*websocket.Conn]*models.Client, remainingSeconds int, mode string, session int) bool {
|
func startTimer(clients map[*websocket.Conn]*models.Client, remainingSeconds int, mode string, session int, totalSessions int) bool {
|
||||||
for remainingSeconds > 0 {
|
for remainingSeconds > 0 {
|
||||||
select {
|
select {
|
||||||
case <-pomodoroResetChannel:
|
case <-pomodoroResetChannel:
|
||||||
|
@ -21,10 +21,10 @@ func startTimer(clients map[*websocket.Conn]*models.Client, remainingSeconds int
|
||||||
// Broadcast the current state to all clients
|
// Broadcast the current state to all clients
|
||||||
if !IsPomodoroPaused() {
|
if !IsPomodoroPaused() {
|
||||||
broadcast.BroadcastMessage(clients, models.ServerMessage{
|
broadcast.BroadcastMessage(clients, models.ServerMessage{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Session: session,
|
Session: session,
|
||||||
MaxSession: PomodoroConfig.Sessions,
|
TotalSession: totalSessions,
|
||||||
TimeLeft: remainingSeconds,
|
TimeLeft: remainingSeconds,
|
||||||
})
|
})
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
remainingSeconds--
|
remainingSeconds--
|
||||||
|
@ -34,10 +34,10 @@ func startTimer(clients map[*websocket.Conn]*models.Client, remainingSeconds int
|
||||||
|
|
||||||
// Final broadcast when time reaches zero
|
// Final broadcast when time reaches zero
|
||||||
broadcast.BroadcastMessage(clients, models.ServerMessage{
|
broadcast.BroadcastMessage(clients, models.ServerMessage{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Session: session,
|
Session: session,
|
||||||
MaxSession: PomodoroConfig.Sessions,
|
TotalSession: totalSessions,
|
||||||
TimeLeft: 0,
|
TimeLeft: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -8,9 +8,19 @@ import (
|
||||||
"log"
|
"log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var unsetPomodoroConfig models.GoTomatoPomodoroConfig // used to check if client passed a config json
|
||||||
|
|
||||||
// handleClientCommands listens for commands from WebSocket clients and dispatches to the timer.
|
// handleClientCommands listens for commands from WebSocket clients and dispatches to the timer.
|
||||||
func handleClientCommands(ws *websocket.Conn) {
|
func handleClientCommands(ws *websocket.Conn) {
|
||||||
for {
|
for {
|
||||||
|
var clientCommand models.ClientCommand
|
||||||
|
var pomodoroConfig = models.GoTomatoPomodoroConfig{
|
||||||
|
Work: 25 * 60,
|
||||||
|
ShortBreak: 5 * 60,
|
||||||
|
LongBreak: 15 * 60,
|
||||||
|
Sessions: 4,
|
||||||
|
}
|
||||||
|
|
||||||
_, message, err := ws.ReadMessage()
|
_, message, err := ws.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Client disconnected: %v", err)
|
log.Printf("Client disconnected: %v", err)
|
||||||
|
@ -19,18 +29,20 @@ func handleClientCommands(ws *websocket.Conn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle incoming commands
|
// Handle incoming commands
|
||||||
var command models.ClientCommand
|
err = json.Unmarshal(message, &clientCommand)
|
||||||
err = json.Unmarshal(message, &command)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error unmarshalling command: %v", err)
|
log.Printf("Error unmarshalling command: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the command
|
// Process the command
|
||||||
switch command.Command {
|
switch clientCommand.Command {
|
||||||
case "start":
|
case "start":
|
||||||
if !pomodoro.IsPomodoroOngoing() {
|
if !pomodoro.IsPomodoroOngoing() {
|
||||||
go pomodoro.RunPomodoro(Clients) // Start the timer with the list of clients
|
if clientCommand.Config != unsetPomodoroConfig {
|
||||||
|
pomodoroConfig = clientCommand.Config
|
||||||
|
}
|
||||||
|
go pomodoro.RunPomodoro(Clients, pomodoroConfig) // Start the timer with the list of clients
|
||||||
}
|
}
|
||||||
case "stop":
|
case "stop":
|
||||||
if pomodoro.IsPomodoroOngoing() {
|
if pomodoro.IsPomodoroOngoing() {
|
||||||
|
|
|
@ -8,7 +8,8 @@ import (
|
||||||
|
|
||||||
// ClientCommand represents a command from the client (start/stop).
|
// ClientCommand represents a command from the client (start/stop).
|
||||||
type ClientCommand struct {
|
type ClientCommand struct {
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
|
Config GoTomatoPomodoroConfig `json:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
|
|
@ -2,8 +2,8 @@ package models
|
||||||
|
|
||||||
// ServerMessage represents the data sent to the client via WebSocket.
|
// ServerMessage represents the data sent to the client via WebSocket.
|
||||||
type ServerMessage struct {
|
type ServerMessage struct {
|
||||||
Mode string `json:"mode"` // "Work", "ShortBreak", or "LongBreak"
|
Mode string `json:"mode"` // "Work", "ShortBreak", or "LongBreak"
|
||||||
Session int `json:"session"` // Current session number
|
Session int `json:"session"` // Current session number
|
||||||
MaxSession int `json:"max_session"` // Total number of sessions
|
TotalSession int `json:"total_sessions"` // Total number of sessions
|
||||||
TimeLeft int `json:"time_left"` // Remaining time in seconds
|
TimeLeft int `json:"time_left"` // Remaining time in seconds
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue