diff --git a/com.pdma.notion-timer.sdPlugin/bin/plugin.js b/com.pdma.notion-timer.sdPlugin/bin/plugin.js index 2ada462..ec3f8d8 100644 --- a/com.pdma.notion-timer.sdPlugin/bin/plugin.js +++ b/com.pdma.notion-timer.sdPlugin/bin/plugin.js @@ -6438,7 +6438,7 @@ async function stopTimer(token, entryId) { } // src/plugin.ts -var CURRENT_VERSION = "1.0.14"; +var CURRENT_VERSION = "1.0.15"; var GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/master"; var SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4= @@ -6492,6 +6492,19 @@ async function getGlobal() { const stored = await plugin_default.settings.getGlobalSettings(); return { ...stored, ...HARDCODED }; } +var memRunningEntryId = void 0; +async function getRunningEntryId() { + if (memRunningEntryId === void 0) { + const stored = await plugin_default.settings.getGlobalSettings(); + memRunningEntryId = stored.runningEntryId ?? null; + } + return memRunningEntryId; +} +async function setRunningEntry(entryId) { + memRunningEntryId = entryId; + const stored = await plugin_default.settings.getGlobalSettings(); + await plugin_default.settings.setGlobalSettings({ ...stored, runningEntryId: entryId }); +} function isConfigured(g) { return !!(g.notionToken && g.userId); } @@ -6501,13 +6514,22 @@ function buttonTitle(projectName) { var TimerToggle = class extends SingletonAction { settingsCache = /* @__PURE__ */ new Map(); async onWillAppear(ev) { - this.settingsCache.set(ev.action.id, ev.payload.settings); const { activeEntryId, projectName } = ev.payload.settings; const title = buttonTitle(projectName || ""); - if (activeEntryId) { - await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]); - } else { + const running = await getRunningEntryId(); + const isRunning = !!activeEntryId && activeEntryId === running; + if (activeEntryId && !isRunning) { + const cleared = { ...ev.payload.settings, activeEntryId: null }; + await ev.action.setSettings(cleared); + this.settingsCache.set(ev.action.id, cleared); await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]); + } else { + this.settingsCache.set(ev.action.id, ev.payload.settings); + if (isRunning) { + await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]); + } else { + await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]); + } } } async onPropertyInspectorDidAppear(ev) { @@ -6551,18 +6573,21 @@ var TimerToggle = class extends SingletonAction { this.settingsCache.set(ev.action.id, stopped); await ev.action.setState(0); await ev.action.setTitle(title); - await ev.action.showOk(); + await setRunningEntry(null); } else { - for (const other of this.actions) { - if (other.id === ev.action.id) continue; - const otherSettings = this.settingsCache.get(other.id); - if (otherSettings?.activeEntryId) { - await stopTimer(global.notionToken, otherSettings.activeEntryId); - const stopped = { ...otherSettings, activeEntryId: null }; - await other.setSettings(stopped); - this.settingsCache.set(other.id, stopped); - await other.setState(0); - await other.setTitle(buttonTitle(otherSettings.projectName || "")); + const prevEntryId = await getRunningEntryId(); + if (prevEntryId) { + await stopTimer(global.notionToken, prevEntryId); + for (const other of this.actions) { + if (other.id === ev.action.id) continue; + const otherSettings = this.settingsCache.get(other.id); + if (otherSettings?.activeEntryId === prevEntryId) { + const stopped = { ...otherSettings, activeEntryId: null }; + await other.setSettings(stopped); + this.settingsCache.set(other.id, stopped); + await other.setState(0); + await other.setTitle(buttonTitle(otherSettings.projectName || "")); + } } } const entryId = await startTimer( @@ -6575,9 +6600,9 @@ var TimerToggle = class extends SingletonAction { const started = { ...ev.payload.settings, activeEntryId: entryId }; await ev.action.setSettings(started); this.settingsCache.set(ev.action.id, started); + await setRunningEntry(entryId); await ev.action.setState(1); await ev.action.setTitle(`\u23F1 ${title}`); - await ev.action.showOk(); } } catch (err) { plugin_default.logger.error("Timer toggle failed:", err); diff --git a/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig b/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig index c074d10..99f1a51 100644 Binary files a/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig and b/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig differ diff --git a/com.pdma.notion-timer.sdPlugin/imgs/idle.png b/com.pdma.notion-timer.sdPlugin/imgs/idle.png new file mode 100644 index 0000000..58ad4aa Binary files /dev/null and b/com.pdma.notion-timer.sdPlugin/imgs/idle.png differ diff --git a/com.pdma.notion-timer.sdPlugin/imgs/idle.svg b/com.pdma.notion-timer.sdPlugin/imgs/idle.svg deleted file mode 100644 index bfa20b7..0000000 --- a/com.pdma.notion-timer.sdPlugin/imgs/idle.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/com.pdma.notion-timer.sdPlugin/imgs/running.png b/com.pdma.notion-timer.sdPlugin/imgs/running.png new file mode 100644 index 0000000..8c20666 Binary files /dev/null and b/com.pdma.notion-timer.sdPlugin/imgs/running.png differ diff --git a/com.pdma.notion-timer.sdPlugin/imgs/running.svg b/com.pdma.notion-timer.sdPlugin/imgs/running.svg deleted file mode 100644 index 1b8feeb..0000000 --- a/com.pdma.notion-timer.sdPlugin/imgs/running.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/notion-timer.streamDeckPlugin b/notion-timer.streamDeckPlugin index 915a2f1..36aadc8 100644 Binary files a/notion-timer.streamDeckPlugin and b/notion-timer.streamDeckPlugin differ diff --git a/src/plugin.ts b/src/plugin.ts index a224aa4..4c0599b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,4 @@ -const CURRENT_VERSION = "1.0.14"; +const CURRENT_VERSION = "1.0.15"; const GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/master"; const SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4= @@ -66,6 +66,7 @@ interface GlobalSettings { timingDbId: string; projectsDbId: string; userId: string; + runningEntryId?: string | null; } interface TimerSettings { @@ -84,6 +85,23 @@ async function getGlobal(): Promise { return { ...stored, ...HARDCODED }; } +// In-memory cache so onWillAppear can check running state without an async round-trip +let memRunningEntryId: string | null | undefined = undefined; // undefined = not yet loaded + +async function getRunningEntryId(): Promise { + if (memRunningEntryId === undefined) { + const stored = await streamDeck.settings.getGlobalSettings(); + memRunningEntryId = stored.runningEntryId ?? null; + } + return memRunningEntryId; +} + +async function setRunningEntry(entryId: string | null): Promise { + memRunningEntryId = entryId; + const stored = await streamDeck.settings.getGlobalSettings(); + await streamDeck.settings.setGlobalSettings({ ...stored, runningEntryId: entryId }); +} + function isConfigured(g: GlobalSettings): boolean { return !!(g.notionToken && g.userId); } @@ -98,13 +116,26 @@ class TimerToggle extends SingletonAction { private settingsCache = new Map(); async onWillAppear(ev: WillAppearEvent): Promise { - this.settingsCache.set(ev.action.id, ev.payload.settings); const { activeEntryId, projectName } = ev.payload.settings; const title = buttonTitle(projectName || ""); - if (activeEntryId) { - await Promise.all([ev.action.setState(1), ev.action.setTitle(`⏱ ${title}`)]); - } else { + + // Use in-memory cache to determine correct state before rendering — no flash + const running = await getRunningEntryId(); + const isRunning = !!activeEntryId && activeEntryId === running; + + if (activeEntryId && !isRunning) { + // Self-heal: this button thinks it's running but it's not — clear it + const cleared = { ...ev.payload.settings, activeEntryId: null }; + await ev.action.setSettings(cleared); + this.settingsCache.set(ev.action.id, cleared); await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]); + } else { + this.settingsCache.set(ev.action.id, ev.payload.settings); + if (isRunning) { + await Promise.all([ev.action.setState(1), ev.action.setTitle(`⏱ ${title}`)]); + } else { + await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]); + } } } @@ -153,19 +184,23 @@ class TimerToggle extends SingletonAction { this.settingsCache.set(ev.action.id, stopped); await ev.action.setState(0); await ev.action.setTitle(title); - await ev.action.showOk(); + await setRunningEntry(null); } else { - // Stop any other running timer first - for (const other of this.actions) { - if (other.id === ev.action.id) continue; - const otherSettings = this.settingsCache.get(other.id); - if (otherSettings?.activeEntryId) { - await stopTimer(global.notionToken, otherSettings.activeEntryId); - const stopped = { ...otherSettings, activeEntryId: null }; - await other.setSettings(stopped); - this.settingsCache.set(other.id, stopped); - await other.setState(0); - await other.setTitle(buttonTitle(otherSettings.projectName || "")); + const prevEntryId = await getRunningEntryId(); + + // Stop previous timer + if (prevEntryId) { + await stopTimer(global.notionToken, prevEntryId); + for (const other of this.actions) { + if (other.id === ev.action.id) continue; + const otherSettings = this.settingsCache.get(other.id); + if (otherSettings?.activeEntryId === prevEntryId) { + const stopped = { ...otherSettings, activeEntryId: null }; + await other.setSettings(stopped); + this.settingsCache.set(other.id, stopped); + await other.setState(0); + await other.setTitle(buttonTitle(otherSettings.projectName || "")); + } } } @@ -179,9 +214,9 @@ class TimerToggle extends SingletonAction { const started = { ...ev.payload.settings, activeEntryId: entryId }; await ev.action.setSettings(started); this.settingsCache.set(ev.action.id, started); + await setRunningEntry(entryId); await ev.action.setState(1); await ev.action.setTitle(`⏱ ${title}`); - await ev.action.showOk(); } } catch (err) { streamDeck.logger.error("Timer toggle failed:", err); diff --git a/version.json b/version.json index e9ea986..b0b51e1 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{ "version": "1.0.14" } +{ "version": "1.0.15" }