Compare commits

..

2 Commits

Author SHA1 Message Date
pdmarf
ea15f72215 Merge branch 'stable-rebuild' 2026-04-24 09:05:44 +01:00
pdmarf
2fd2b6ad8a v1.0.33: instant button feedback on key press
Visual state change now happens before any async work (getGlobal,
getRunningEntryId, API calls). Previously the button waited for
getGlobal() to resolve before going green, causing a 1-2s delay.
Also reverts optimistic state on API error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 09:05:41 +01:00
6 changed files with 31 additions and 21 deletions

View File

@@ -6438,7 +6438,7 @@ async function stopTimer(token, entryId) {
} }
// src/plugin.ts // src/plugin.ts
var CURRENT_VERSION = "1.0.32"; var CURRENT_VERSION = "1.0.33";
var GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/stable-rebuild"; var GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/stable-rebuild";
var SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- var SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4= MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4=
@@ -6593,10 +6593,18 @@ var TimerToggle = class extends SingletonAction {
} }
async onKeyDown(ev) { async onKeyDown(ev) {
this.settingsCache.set(ev.action.id, ev.payload.settings); this.settingsCache.set(ev.action.id, ev.payload.settings);
const global = await getGlobal();
const { projectId, projectName, activeEntryId } = ev.payload.settings; const { projectId, projectName, activeEntryId } = ev.payload.settings;
const title = buttonTitle(projectName || ""); const title = buttonTitle(projectName || "");
if (projectId) {
if (activeEntryId) {
await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]);
} else {
await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]);
}
}
const global = await getGlobal();
if (!isConfigured(global)) { if (!isConfigured(global)) {
await Promise.all([ev.action.setState(activeEntryId ? 1 : 0), ev.action.setTitle(activeEntryId ? `\u23F1 ${title}` : title)]);
await ev.action.showAlert(); await ev.action.showAlert();
return; return;
} }
@@ -6610,8 +6618,6 @@ var TimerToggle = class extends SingletonAction {
const stopped = { ...ev.payload.settings, activeEntryId: null }; const stopped = { ...ev.payload.settings, activeEntryId: null };
await ev.action.setSettings(stopped); await ev.action.setSettings(stopped);
this.settingsCache.set(ev.action.id, stopped); this.settingsCache.set(ev.action.id, stopped);
await ev.action.setState(0);
await ev.action.setTitle(title);
await setRunningEntry(null); await setRunningEntry(null);
} else { } else {
const prevEntryId = await getRunningEntryId(); const prevEntryId = await getRunningEntryId();
@@ -6623,9 +6629,6 @@ var TimerToggle = class extends SingletonAction {
await Promise.all([other.setState(0), other.setTitle(buttonTitle(otherSettings.projectName || ""))]); await Promise.all([other.setState(0), other.setTitle(buttonTitle(otherSettings.projectName || ""))]);
} }
} }
}
await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]);
if (prevEntryId) {
await stopTimer(global.notionToken, prevEntryId); await stopTimer(global.notionToken, prevEntryId);
for (const other of this.actions) { for (const other of this.actions) {
if (other.id === ev.action.id) continue; if (other.id === ev.action.id) continue;
@@ -6651,6 +6654,7 @@ var TimerToggle = class extends SingletonAction {
await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]); await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]);
} }
} catch (err) { } catch (err) {
await Promise.all([ev.action.setState(activeEntryId ? 1 : 0), ev.action.setTitle(activeEntryId ? `\u23F1 ${title}` : title)]);
plugin_default.logger.error("Timer toggle failed:", err); plugin_default.logger.error("Timer toggle failed:", err);
await ev.action.showAlert(); await ev.action.showAlert();
} }

View File

@@ -1 +1 @@
ôÙ"Ë4-Ħ8peŒ—Oc"ÜuÉ»SÀ<53>>gé‡-ŸÁ#—&<26>Óò¿q‰¡N0Åý”ÒvZ¶ÍÎI²<49>ß ÜYÈZ‡Ã¾÷G¡ã¯Ã¦9â¹¸ß ]W¬çjB=1ø™+n‡A ;%:`k·„¿ç°õÀÓáæ

View File

@@ -2,7 +2,7 @@
"Author": "Pete Marfleet", "Author": "Pete Marfleet",
"Description": "Toggle Notion time tracking for a project with a single button press.", "Description": "Toggle Notion time tracking for a project with a single button press.",
"Name": "Notion Timer", "Name": "Notion Timer",
"Version": "1.0.32", "Version": "1.0.33",
"SDKVersion": 2, "SDKVersion": 2,
"Software": { "MinimumVersion": "5.0" }, "Software": { "MinimumVersion": "5.0" },
"OS": [{ "Platform": "mac", "MinimumVersion": "10.11" }], "OS": [{ "Platform": "mac", "MinimumVersion": "10.11" }],

Binary file not shown.

View File

@@ -1,4 +1,4 @@
const CURRENT_VERSION = "1.0.32"; const CURRENT_VERSION = "1.0.33";
const GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/stable-rebuild"; const GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/stable-rebuild";
const SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- const SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4= MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4=
@@ -198,11 +198,22 @@ class TimerToggle extends SingletonAction<TimerSettings> {
async onKeyDown(ev: KeyDownEvent<TimerSettings>): Promise<void> { async onKeyDown(ev: KeyDownEvent<TimerSettings>): Promise<void> {
this.settingsCache.set(ev.action.id, ev.payload.settings); this.settingsCache.set(ev.action.id, ev.payload.settings);
const global = await getGlobal();
const { projectId, projectName, activeEntryId } = ev.payload.settings; const { projectId, projectName, activeEntryId } = ev.payload.settings;
const title = buttonTitle(projectName || ""); const title = buttonTitle(projectName || "");
// Instant visual feedback before any async work
if (projectId) {
if (activeEntryId) {
await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]);
} else {
await Promise.all([ev.action.setState(1), ev.action.setTitle(`${title}`)]);
}
}
const global = await getGlobal();
if (!isConfigured(global)) { if (!isConfigured(global)) {
await Promise.all([ev.action.setState(activeEntryId ? 1 : 0), ev.action.setTitle(activeEntryId ? `${title}` : title)]);
await ev.action.showAlert(); await ev.action.showAlert();
return; return;
} }
@@ -218,13 +229,11 @@ class TimerToggle extends SingletonAction<TimerSettings> {
const stopped = { ...ev.payload.settings, activeEntryId: null }; const stopped = { ...ev.payload.settings, activeEntryId: null };
await ev.action.setSettings(stopped); await ev.action.setSettings(stopped);
this.settingsCache.set(ev.action.id, stopped); this.settingsCache.set(ev.action.id, stopped);
await ev.action.setState(0);
await ev.action.setTitle(title);
await setRunningEntry(null); await setRunningEntry(null);
} else { } else {
const prevEntryId = await getRunningEntryId(); const prevEntryId = await getRunningEntryId();
// Optimistically update visuals immediately — no waiting for API // Turn off the previously running button immediately
if (prevEntryId) { if (prevEntryId) {
for (const other of this.actions) { for (const other of this.actions) {
if (other.id === ev.action.id) continue; if (other.id === ev.action.id) continue;
@@ -233,11 +242,6 @@ class TimerToggle extends SingletonAction<TimerSettings> {
await Promise.all([other.setState(0), other.setTitle(buttonTitle(otherSettings.projectName || ""))]); await Promise.all([other.setState(0), other.setTitle(buttonTitle(otherSettings.projectName || ""))]);
} }
} }
}
await Promise.all([ev.action.setState(1), ev.action.setTitle(`${title}`)]);
// Now do the API calls
if (prevEntryId) {
await stopTimer(global.notionToken, prevEntryId); await stopTimer(global.notionToken, prevEntryId);
for (const other of this.actions) { for (const other of this.actions) {
if (other.id === ev.action.id) continue; if (other.id === ev.action.id) continue;
@@ -261,10 +265,12 @@ class TimerToggle extends SingletonAction<TimerSettings> {
await ev.action.setSettings(started); await ev.action.setSettings(started);
this.settingsCache.set(ev.action.id, started); this.settingsCache.set(ev.action.id, started);
await setRunningEntry(entryId); await setRunningEntry(entryId);
// Re-assert state after setSettings, which can reset visual state // Re-assert after setSettings resets visual state
await Promise.all([ev.action.setState(1), ev.action.setTitle(`${title}`)]); await Promise.all([ev.action.setState(1), ev.action.setTitle(`${title}`)]);
} }
} catch (err) { } catch (err) {
// Revert optimistic visual on error
await Promise.all([ev.action.setState(activeEntryId ? 1 : 0), ev.action.setTitle(activeEntryId ? `${title}` : title)]);
streamDeck.logger.error("Timer toggle failed:", err); streamDeck.logger.error("Timer toggle failed:", err);
await ev.action.showAlert(); await ev.action.showAlert();
} }

View File

@@ -1 +1 @@
{ "version": "1.0.32" } { "version": "1.0.33" }