Add pomodoro plugin (port from legacy v4 plugin) (#69)
* add pomodoro plugin * add thumbnail.webp * fix translations * fix translation key * add required sections to README, add deps field to manifest * small fixes * fix config keys * review fixes * fix(readme) typos --------- Co-authored-by: Kirill Sergeev <sergeev@ap-team.ru> Co-authored-by: Lemmy <studio@quadbyte.net>
This commit is contained in:
co-authored by
Kirill Sergeev
Lemmy
parent
12532101dd
commit
144e7209ca
Binary file not shown.
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Kirill Sergeev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Pomodoro Timer
|
||||||
|
|
||||||
|
A Pomodoro timer plugin for Noctalia for productivity. Initially ported from the legacy v4 plugin [Pomodoro Timer](https://github.com/noctalia-dev/legacy-v4-plugins/tree/main/pomodoro).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Sessions**: Configurable sessions based on the standard format (work - short break - long break). Durations can be configured in the settings.
|
||||||
|
- **Cycles**: Configurable number of (work - short break) cycles before a long break.
|
||||||
|
- **Auto-start**: Optionally auto-start breaks and/or work sessions.
|
||||||
|
- **Bar Widget**: Shows status and remaining time on the bar widget when the panel is closed.
|
||||||
|
- **Notifications**: Toast notification when work/break finishes.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- Sound notification (currently the toast is shown silently)
|
||||||
|
- IPC
|
||||||
|
|
||||||
|
## Plugin
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | `thepunkoff/pomodoro` |
|
||||||
|
| Entries | Bar widget: `widget`; panel: `panel`; service: `pomodoro` |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
1. Enable plugin in settings
|
||||||
|
2. Add bar widget `Pomodoro Timer`
|
||||||
|
3. Widget appears on the bar, clicking it will toggle the panel.
|
||||||
|
|
||||||
|
To open the panel with a command:
|
||||||
|
```sh
|
||||||
|
noctalia msg panel-toggle thepunkoff/pomodoro:panel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `work-duration` | `int` | `25` | Duration of each work session in minutes. |
|
||||||
|
| `short-break-duration` | `int` | `5` | Duration of short breaks in minutes. |
|
||||||
|
| `long-break-duration` | `int` | `15` | Duration of long breaks in minutes. |
|
||||||
|
| `sessions-before-long-break` | `int` | `4` | Number of sessions before a long break (min=1). |
|
||||||
|
| `auto-start-work` | `bool` | `false` | Automatically start the work timer after a break. |
|
||||||
|
| `auto-start-breaks` | `bool` | `false` | Automatically start the break timer after a work session. |
|
||||||
|
|
||||||
|
## IPC
|
||||||
|
```sh
|
||||||
|
noctalia msg panel-toggle thepunkoff/pomodoro:panel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
This project is licensed under the MIT License.
|
||||||
|
|
||||||
|
It bundles the JetBrains Mono font, which is licensed separately under the SIL Open Font License 1.1 (OFL-1.1). See `THIRD_PARTY_LICENCES/OFL.txt` for the full license text.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
local timeFontFamily = noctalia.loadFont("JetBrainsMono-Regular.ttf")
|
||||||
|
|
||||||
|
local function formatTime(seconds, total)
|
||||||
|
local showingHours = total >= 3600 or seconds >= 3600
|
||||||
|
local hours = math.floor(seconds / 3600)
|
||||||
|
local mins = math.floor((seconds % 3600) / 60)
|
||||||
|
local secs = seconds % 60
|
||||||
|
|
||||||
|
if showingHours then
|
||||||
|
return string.format("%d:%02d:%02d", hours, mins, secs)
|
||||||
|
else
|
||||||
|
return string.format("%02d:%02d", mins, secs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function onStartButtonClick()
|
||||||
|
noctalia.state.set("pomodoro.nextCommand", "toggle")
|
||||||
|
end
|
||||||
|
|
||||||
|
function onSkipButtonClick()
|
||||||
|
noctalia.state.set("pomodoro.nextCommand", "skip")
|
||||||
|
end
|
||||||
|
|
||||||
|
function onResetButtonClick()
|
||||||
|
noctalia.state.set("pomodoro.nextCommand", "reset")
|
||||||
|
end
|
||||||
|
|
||||||
|
function onResetAllButtonClick()
|
||||||
|
noctalia.state.set("pomodoro.nextCommand", "resetAll")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function render(state, sessionData)
|
||||||
|
local sessionNumber = state.sessionPtr.session
|
||||||
|
local session = sessionData[sessionNumber]
|
||||||
|
local stageNumber = state.sessionPtr.stage
|
||||||
|
local stageTotalSeconds = session[stageNumber]
|
||||||
|
|
||||||
|
local stageName =
|
||||||
|
if stageNumber == 1 then
|
||||||
|
noctalia.tr("work")
|
||||||
|
else if sessionNumber < #sessionData then
|
||||||
|
noctalia.tr("short-break")
|
||||||
|
else
|
||||||
|
noctalia.tr("long-break")
|
||||||
|
|
||||||
|
local startButtonText =
|
||||||
|
if state.isRunning then
|
||||||
|
noctalia.tr("button.pause")
|
||||||
|
else if state.secondsLeft > 0 and state.secondsLeft < stageTotalSeconds then
|
||||||
|
noctalia.tr("button.resume")
|
||||||
|
else
|
||||||
|
noctalia.tr("button.start")
|
||||||
|
|
||||||
|
local startButtonGlyph = if state.isRunning then "player-pause" else "player-play"
|
||||||
|
|
||||||
|
local skipButtonEnabled = state.isDirty and (if state.isRunning then state.secondsLeft > 0 else stageTotalSeconds > 0)
|
||||||
|
|
||||||
|
local resetButtonEnabled = state.isDirty and state.secondsLeft > 0
|
||||||
|
|
||||||
|
local resetAllButtonEnabled = state.isDirty and (state.isRunning or state.secondsLeft > 0 or (state.sessionPtr.session > 1 and state.sessionPtr.stage > 1))
|
||||||
|
|
||||||
|
local stageIcon = if state.sessionPtr.stage == 1 then "brain" else "coffee"
|
||||||
|
|
||||||
|
panel.render(
|
||||||
|
ui.column({ flexGrow = 1, gap = 16 }, {
|
||||||
|
-- header
|
||||||
|
ui.row({ align = "center", gap = 8 }, {
|
||||||
|
ui.glyph({ name = stageIcon, color = "primary" }),
|
||||||
|
|
||||||
|
ui.label({
|
||||||
|
text = "Pomodoro",
|
||||||
|
fontSize = 18,
|
||||||
|
fontWeight = "bold",
|
||||||
|
color = "on_surface",
|
||||||
|
flexGrow = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ui.label({
|
||||||
|
text = string.format("%s %d/%d", noctalia.tr("session"), sessionNumber, #sessionData),
|
||||||
|
fontSize = 12,
|
||||||
|
color = "on_surface_variant",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
-- time
|
||||||
|
ui.column({ flexGrow = 1, align = "center", justify = "center", }, {
|
||||||
|
ui.label({
|
||||||
|
text = stageName,
|
||||||
|
fontSize = 18,
|
||||||
|
fontWeight = "medium",
|
||||||
|
color = "primary",
|
||||||
|
}),
|
||||||
|
|
||||||
|
ui.label({
|
||||||
|
text = formatTime(state.secondsLeft, stageTotalSeconds),
|
||||||
|
fontSize = 42,
|
||||||
|
fontWeight = "bold",
|
||||||
|
fontFamily = timeFontFamily,
|
||||||
|
color = "primary",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
-- buttons
|
||||||
|
ui.column({ gap = 8 }, {
|
||||||
|
ui.row({ gap = 8, justify = "space_between" }, {
|
||||||
|
ui.button({
|
||||||
|
variant = "primary",
|
||||||
|
text = startButtonText,
|
||||||
|
glyph = startButtonGlyph,
|
||||||
|
onClick = "onStartButtonClick",
|
||||||
|
flexGrow = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ui.button({
|
||||||
|
text = noctalia.tr("button.skip"),
|
||||||
|
glyph = "player-skip-forward",
|
||||||
|
onClick = "onSkipButtonClick",
|
||||||
|
enabled = skipButtonEnabled,
|
||||||
|
flexGrow = 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
ui.row({ gap = 8, justify = "space_between" }, {
|
||||||
|
ui.button({
|
||||||
|
text = noctalia.tr("button.reset"),
|
||||||
|
glyph = "refresh",
|
||||||
|
onClick = "onResetButtonClick",
|
||||||
|
enabled = resetButtonEnabled,
|
||||||
|
flexGrow = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
ui.button({
|
||||||
|
text = noctalia.tr("button.reset-all"),
|
||||||
|
glyph = "rotate",
|
||||||
|
onClick = "onResetAllButtonClick",
|
||||||
|
enabled = resetAllButtonEnabled,
|
||||||
|
flexGrow = 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}) -- column
|
||||||
|
) -- panel
|
||||||
|
end
|
||||||
|
|
||||||
|
function onOpen(context)
|
||||||
|
local state = noctalia.state.get("pomodoro.state")
|
||||||
|
local sessionData = noctalia.state.get("pomodoro.sessionData")
|
||||||
|
render(state, sessionData)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function notifyStageFinished(state, sessionData)
|
||||||
|
local sessionNumber = state.sessionPtr.session
|
||||||
|
local stageNumber = state.sessionPtr.stage
|
||||||
|
local body =
|
||||||
|
if stageNumber == 1 then
|
||||||
|
noctalia.tr("notification.work-complete")
|
||||||
|
elseif sessionNumber < #sessionData then
|
||||||
|
noctalia.tr("notification.short-break-complete")
|
||||||
|
else
|
||||||
|
noctalia.tr("notification.long-break-complete")
|
||||||
|
noctalia.notify("Pomodoro Timer", body)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- main
|
||||||
|
noctalia.state.watch("pomodoro.state", function(state)
|
||||||
|
local config = noctalia.state.get("pomodoro.config")
|
||||||
|
local sessionData = noctalia.state.get("pomodoro.sessionData")
|
||||||
|
|
||||||
|
if state.secondsLeft == 0 then
|
||||||
|
notifyStageFinished(state, sessionData)
|
||||||
|
noctalia.state.set("pomodoro.nextCommand", "skip")
|
||||||
|
if (config.autoStartWork and state.sessionPtr.stage == 2) or (config.autoStartBreaks and state.sessionPtr.stage == 1) then
|
||||||
|
noctalia.state.set("pomodoro.nextCommand", "toggle")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render(state, sessionData)
|
||||||
|
end)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
id = "thepunkoff/pomodoro"
|
||||||
|
name = "Pomodoro Timer"
|
||||||
|
version = "1.0.0"
|
||||||
|
plugin_api = 3
|
||||||
|
author = "thepunkoff"
|
||||||
|
license = "MIT"
|
||||||
|
icon = "brain"
|
||||||
|
description = "Simple pomodoro timer."
|
||||||
|
tags = ["bar", "panel", "service", "productivity", "time"]
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "work-duration"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.work-duration.label"
|
||||||
|
description_key = "settings.work-duration.description"
|
||||||
|
default = 25
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "short-break-duration"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.short-break-duration.label"
|
||||||
|
description_key = "settings.short-break-duration.description"
|
||||||
|
default = 5
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "long-break-duration"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.long-break-duration.label"
|
||||||
|
description_key = "settings.long-break-duration.description"
|
||||||
|
default = 15
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "sessions-before-long-break"
|
||||||
|
type = "int"
|
||||||
|
label_key = "settings.sessions-before-long-break.label"
|
||||||
|
description_key = "settings.sessions-before-long-break.description"
|
||||||
|
default = 4
|
||||||
|
min = 1
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "auto-start-breaks"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "settings.auto-start-breaks.label"
|
||||||
|
description_key = "settings.auto-start-breaks.description"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "auto-start-work"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "settings.auto-start-work.label"
|
||||||
|
description_key = "settings.auto-start-work.description"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[widget]]
|
||||||
|
id = "widget"
|
||||||
|
entry = "widget.luau"
|
||||||
|
|
||||||
|
[[panel]]
|
||||||
|
id = "panel"
|
||||||
|
entry = "panel.luau"
|
||||||
|
placement = "attached"
|
||||||
|
open_near_click = true
|
||||||
|
width = 350
|
||||||
|
height = 250
|
||||||
|
|
||||||
|
[[service]]
|
||||||
|
id = "pomodoro"
|
||||||
|
entry = "pomodoro.luau"
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
local config = (function()
|
||||||
|
return {
|
||||||
|
workDuration = noctalia.getConfig("work-duration"),
|
||||||
|
shortBreakDuration = noctalia.getConfig("short-break-duration"),
|
||||||
|
longBreakDuration = noctalia.getConfig("long-break-duration"),
|
||||||
|
sessionsBeforeLongBreak = noctalia.getConfig("sessions-before-long-break"),
|
||||||
|
autoStartBreaks = noctalia.getConfig("auto-start-breaks"),
|
||||||
|
autoStartWork = noctalia.getConfig("auto-start-work"),
|
||||||
|
}
|
||||||
|
end)()
|
||||||
|
noctalia.state.set("pomodoro.config", config)
|
||||||
|
|
||||||
|
local sessionData = {}
|
||||||
|
for i = 1, config.sessionsBeforeLongBreak do
|
||||||
|
breakDuration = if i < config.sessionsBeforeLongBreak then config.shortBreakDuration else config.longBreakDuration
|
||||||
|
table.insert(sessionData, { config.workDuration * 60, breakDuration * 60 })
|
||||||
|
end
|
||||||
|
noctalia.state.set("pomodoro.sessionData", sessionData)
|
||||||
|
|
||||||
|
local isRunning = false
|
||||||
|
local sessionPtr = { session = 1, stage = 1 }
|
||||||
|
local isDirty = false
|
||||||
|
local secondsLeft = 0
|
||||||
|
|
||||||
|
local function getStateSnapshot()
|
||||||
|
return {
|
||||||
|
isRunning = isRunning,
|
||||||
|
secondsLeft = secondsLeft,
|
||||||
|
sessionPtr = sessionPtr,
|
||||||
|
isDirty = isDirty,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getStageTotalSeconds()
|
||||||
|
local sessionNumber = sessionPtr.session
|
||||||
|
local session = sessionData[sessionNumber]
|
||||||
|
local stageNumber = sessionPtr.stage
|
||||||
|
return session[stageNumber]
|
||||||
|
end
|
||||||
|
|
||||||
|
--- main
|
||||||
|
secondsLeft = getStageTotalSeconds()
|
||||||
|
|
||||||
|
noctalia.state.watch("pomodoro.nextCommand", function(command)
|
||||||
|
if command == "toggle" then
|
||||||
|
isDirty = true
|
||||||
|
isRunning = not isRunning
|
||||||
|
elseif command == "skip" then
|
||||||
|
isRunning = false
|
||||||
|
if sessionPtr.stage == 1 then
|
||||||
|
sessionPtr.stage = 2
|
||||||
|
elseif sessionPtr.session < #sessionData then
|
||||||
|
sessionPtr.session += 1
|
||||||
|
sessionPtr.stage = 1
|
||||||
|
elseif sessionPtr.session == #sessionData then
|
||||||
|
sessionPtr = { session = 1, stage = 1 }
|
||||||
|
end
|
||||||
|
secondsLeft = getStageTotalSeconds()
|
||||||
|
elseif command == "reset" then
|
||||||
|
isRunning = false
|
||||||
|
secondsLeft = getStageTotalSeconds()
|
||||||
|
elseif command == "resetAll" then
|
||||||
|
isRunning = false
|
||||||
|
sessionPtr = { session = 1, stage = 1 }
|
||||||
|
secondsLeft = getStageTotalSeconds()
|
||||||
|
isDirty = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local state = getStateSnapshot()
|
||||||
|
noctalia.state.set("pomodoro.state", state)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local state = getStateSnapshot()
|
||||||
|
noctalia.state.set("pomodoro.state", state)
|
||||||
|
|
||||||
|
noctalia.setUpdateInterval(1000)
|
||||||
|
function update()
|
||||||
|
if isRunning then
|
||||||
|
secondsLeft -= 1
|
||||||
|
local state = getStateSnapshot()
|
||||||
|
noctalia.state.set("pomodoro.state", state)
|
||||||
|
end
|
||||||
|
end
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"work": "Work",
|
||||||
|
"short-break": "Short Break",
|
||||||
|
"long-break": "Long Break",
|
||||||
|
"session": "Session",
|
||||||
|
"button": {
|
||||||
|
"start": "Start",
|
||||||
|
"resume": "Resume",
|
||||||
|
"pause": "Pause",
|
||||||
|
"skip": "Skip",
|
||||||
|
"reset": "Reset",
|
||||||
|
"reset-all": "Reset All"
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"work-complete": "Work session complete! Time for a break.",
|
||||||
|
"short-break-complete": "Break over! Ready to focus?",
|
||||||
|
"long-break-complete": "Long break over! Ready for a new cycle?"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"work-duration":{
|
||||||
|
"label": "Work Duration",
|
||||||
|
"description": "Duration of each work session in minutes"
|
||||||
|
},
|
||||||
|
"short-break-duration":{
|
||||||
|
"label": "Short Break Duration",
|
||||||
|
"description": "Duration of short breaks in minutes"
|
||||||
|
},
|
||||||
|
"long-break-duration":{
|
||||||
|
"label": "Long Break Duration",
|
||||||
|
"description": "Duration of long breaks in minutes"
|
||||||
|
},
|
||||||
|
"sessions-before-long-break":{
|
||||||
|
"label": "Sessions Before Long Break",
|
||||||
|
"description": "Number of sessions before a long break"
|
||||||
|
},
|
||||||
|
"auto-start-work":{
|
||||||
|
"label": "Auto-start Work",
|
||||||
|
"description": "Automatically start work timer after break"
|
||||||
|
},
|
||||||
|
"auto-start-breaks":{
|
||||||
|
"label": "Auto-start Breaks",
|
||||||
|
"description": "Automatically start break timer after work session"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
function onClick()
|
||||||
|
noctalia.togglePanel("thepunkoff/pomodoro:panel")
|
||||||
|
end
|
||||||
|
|
||||||
|
local isUpdating = false
|
||||||
|
noctalia.state.watch("pomodoro.nextCommand", function(command)
|
||||||
|
if command == "toggle" then
|
||||||
|
isUpdating = true
|
||||||
|
elseif command == "resetAll" then
|
||||||
|
isUpdating = false
|
||||||
|
barWidget.setGlyph("brain")
|
||||||
|
barWidget.setGlyphColor("default")
|
||||||
|
barWidget.setColor("default")
|
||||||
|
barWidget.setText("")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- main
|
||||||
|
barWidget.setGlyph("brain")
|
||||||
|
|
||||||
|
noctalia.state.watch("pomodoro.state", function(state)
|
||||||
|
if not isUpdating then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local stageIcon = if state.sessionPtr.stage == 1 then "brain" else "coffee"
|
||||||
|
barWidget.setGlyph(stageIcon)
|
||||||
|
|
||||||
|
if state.isRunning then
|
||||||
|
barWidget.setGlyphColor("primary")
|
||||||
|
barWidget.setColor("primary")
|
||||||
|
else
|
||||||
|
barWidget.setGlyphColor("default")
|
||||||
|
barWidget.setColor("default")
|
||||||
|
end
|
||||||
|
|
||||||
|
local hours = math.floor(state.secondsLeft / 3600)
|
||||||
|
local mins = math.floor((state.secondsLeft % 3600) / 60)
|
||||||
|
local secs = state.secondsLeft % 60
|
||||||
|
|
||||||
|
local time = if hours == 0 then string.format("%02d:%02d", mins, secs) else string.format("%d:%02d:%02d", hours, mins, secs)
|
||||||
|
barWidget.setText(time)
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user