diff --git a/com.pdma.notion-timer.sdPlugin/bin/plugin.js b/com.pdma.notion-timer.sdPlugin/bin/plugin.js index a2aa645..29dcf3e 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.40"; +var CURRENT_VERSION = "1.0.41"; var GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/stable-rebuild"; var SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4= @@ -6567,9 +6567,61 @@ function isConfigured(g) { function buttonTitle(projectName) { return projectName.replace(/^[\p{Extended_Pictographic}\uFE0F\s]+/u, "").trim(); } +var ELAPSED_CHECK_MS = 60 * 60 * 1e3; +var DIALOG_TIMEOUT_SECONDS = 300; +async function promptContinue(title) { + const { execFile: execFile3 } = await import("node:child_process"); + const safeTitle = title.replace(/[\\"]/g, ""); + const script = `display dialog "Timer for ${safeTitle} has been running for over an hour. + +Should it continue?" buttons {"Stop", "Continue"} default button "Continue" giving up after ${DIALOG_TIMEOUT_SECONDS}`; + return new Promise((resolve) => { + execFile3("osascript", ["-e", script], (err, stdout) => { + if (err || /gave up:true/.test(String(stdout))) { + resolve("timeout"); + return; + } + resolve(/button returned:Stop/.test(String(stdout)) ? "stop" : "continue"); + }); + }); +} var TimerToggle = class extends SingletonAction { projectCache = /* @__PURE__ */ new Map(); pendingKeyDown = /* @__PURE__ */ new Set(); + elapsedCheckTimer = null; + clearElapsedCheck() { + if (this.elapsedCheckTimer) clearTimeout(this.elapsedCheckTimer); + this.elapsedCheckTimer = null; + } + scheduleElapsedCheck(entryId, actionId, title) { + this.clearElapsedCheck(); + this.elapsedCheckTimer = setTimeout(() => { + this.checkElapsed(entryId, actionId, title); + }, ELAPSED_CHECK_MS); + } + async checkElapsed(entryId, actionId, title) { + if (memRunningEntryId !== entryId) return; + const answer = await promptContinue(title); + if (memRunningEntryId !== entryId) return; + if (answer === "continue") { + this.scheduleElapsedCheck(entryId, actionId, title); + return; + } + try { + const global = await getGlobal(); + await stopTimer(global.notionToken, entryId); + } catch (err) { + plugin_default.logger.error("Auto-stop after elapsed-check failed:", err); + } + setRunningEntry(null, null); + this.clearElapsedCheck(); + for (const action2 of this.actions) { + if (action2.id === actionId) { + await Promise.all([action2.setState(0), action2.setTitle(title)]); + break; + } + } + } async onWillAppear(ev) { this.projectCache.set(ev.action.id, ev.payload.settings); const title = buttonTitle(ev.payload.settings.projectName || ""); @@ -6618,13 +6670,16 @@ var TimerToggle = class extends SingletonAction { if (isRunning) { await stopTimer(global.notionToken, memRunningEntryId); setRunningEntry(null, null); + this.clearElapsedCheck(); await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]); } else { if (memRunningEntryId) { await stopTimer(global.notionToken, memRunningEntryId); + this.clearElapsedCheck(); } const entryId = await startTimer(global.notionToken, global.timingDbId, projectId, projectName, global.userId); setRunningEntry(entryId, ev.action.id); + this.scheduleElapsedCheck(entryId, ev.action.id, title); await Promise.all([ev.action.setState(1), ev.action.setTitle(`\u23F1 ${title}`)]); } } catch (err) { diff --git a/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig b/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig index 0af72e1..a10a57b 100644 --- a/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig +++ b/com.pdma.notion-timer.sdPlugin/bin/plugin.js.sig @@ -1 +1,2 @@ -{Qտj? z_DA+Oif&6$ +b;Ќ@b}&X \ No newline at end of file diff --git a/notion-timer.streamDeckPlugin b/notion-timer.streamDeckPlugin index d8a492a..853aae6 100644 Binary files a/notion-timer.streamDeckPlugin and b/notion-timer.streamDeckPlugin differ diff --git a/src/plugin.ts b/src/plugin.ts index c6ba620..05fc141 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,4 @@ -const CURRENT_VERSION = "1.0.40"; +const CURRENT_VERSION = "1.0.41"; const GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/stable-rebuild"; const SIGNING_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAN7ko8TUpuPzPAJuKAZCRjV0c4ZSlou5d9pUAF6o12b4= @@ -164,10 +164,64 @@ function buttonTitle(projectName: string): string { return projectName.replace(/^[\p{Extended_Pictographic}\uFE0F\s]+/u, "").trim(); } +const ELAPSED_CHECK_MS = 60 * 60 * 1000; // 1 hour +const DIALOG_TIMEOUT_SECONDS = 300; // 5 minutes + +async function promptContinue(title: string): Promise<"stop" | "continue" | "timeout"> { + const { execFile } = await import("node:child_process"); + const safeTitle = title.replace(/[\\"]/g, ""); + const script = `display dialog "Timer for ${safeTitle} has been running for over an hour.\n\nShould it continue?" buttons {"Stop", "Continue"} default button "Continue" giving up after ${DIALOG_TIMEOUT_SECONDS}`; + return new Promise((resolve) => { + execFile("osascript", ["-e", script], (err, stdout) => { + if (err || /gave up:true/.test(String(stdout))) { resolve("timeout"); return; } + resolve(/button returned:Stop/.test(String(stdout)) ? "stop" : "continue"); + }); + }); +} + @action({ UUID: "com.pdma.notion-timer.toggle" }) class TimerToggle extends SingletonAction { private projectCache = new Map(); private pendingKeyDown = new Set(); + private elapsedCheckTimer: NodeJS.Timeout | null = null; + + private clearElapsedCheck(): void { + if (this.elapsedCheckTimer) clearTimeout(this.elapsedCheckTimer); + this.elapsedCheckTimer = null; + } + + private scheduleElapsedCheck(entryId: string, actionId: string, title: string): void { + this.clearElapsedCheck(); + this.elapsedCheckTimer = setTimeout(() => { + this.checkElapsed(entryId, actionId, title); + }, ELAPSED_CHECK_MS); + } + + private async checkElapsed(entryId: string, actionId: string, title: string): Promise { + if (memRunningEntryId !== entryId) return; + const answer = await promptContinue(title); + if (memRunningEntryId !== entryId) return; + + if (answer === "continue") { + this.scheduleElapsedCheck(entryId, actionId, title); + return; + } + + try { + const global = await getGlobal(); + await stopTimer(global.notionToken, entryId); + } catch (err) { + streamDeck.logger.error("Auto-stop after elapsed-check failed:", err); + } + setRunningEntry(null, null); + this.clearElapsedCheck(); + for (const action of this.actions) { + if (action.id === actionId) { + await Promise.all([action.setState(0), action.setTitle(title)]); + break; + } + } + } async onWillAppear(ev: WillAppearEvent): Promise { this.projectCache.set(ev.action.id, ev.payload.settings); @@ -217,13 +271,16 @@ class TimerToggle extends SingletonAction { if (isRunning) { await stopTimer(global.notionToken, memRunningEntryId!); setRunningEntry(null, null); + this.clearElapsedCheck(); await Promise.all([ev.action.setState(0), ev.action.setTitle(title)]); } else { if (memRunningEntryId) { await stopTimer(global.notionToken, memRunningEntryId); + this.clearElapsedCheck(); } const entryId = await startTimer(global.notionToken, global.timingDbId, projectId, projectName, global.userId); setRunningEntry(entryId, ev.action.id); + this.scheduleElapsedCheck(entryId, ev.action.id, title); await Promise.all([ev.action.setState(1), ev.action.setTitle(`⏱ ${title}`)]); } } catch (err) { diff --git a/version.json b/version.json index 27d7451..315992c 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{ "version": "1.0.40" } +{ "version": "1.0.41" }