diff --git a/com.pdma.notion-timer.sdPlugin/manifest.json b/com.pdma.notion-timer.sdPlugin/manifest.json index d000408..8461c55 100644 --- a/com.pdma.notion-timer.sdPlugin/manifest.json +++ b/com.pdma.notion-timer.sdPlugin/manifest.json @@ -2,7 +2,7 @@ "Author": "Pete Marfleet", "Description": "Toggle Notion time tracking for a project with a single button press.", "Name": "Notion Timer", - "Version": "1.0.0", + "Version": "1.0.26", "SDKVersion": 2, "Software": { "MinimumVersion": "5.0" }, "OS": [{ "Platform": "mac", "MinimumVersion": "10.11" }], diff --git a/com.pdma.notion-timer.sdPlugin/ui/property-inspector.html b/com.pdma.notion-timer.sdPlugin/ui/property-inspector.html index a1eea66..3d5de35 100644 --- a/com.pdma.notion-timer.sdPlugin/ui/property-inspector.html +++ b/com.pdma.notion-timer.sdPlugin/ui/property-inspector.html @@ -127,7 +127,6 @@ -

Shared across all buttons. Select once per device.


@@ -195,9 +194,6 @@ }; $PI.setGlobalSettings(creds); setCredStatus("Credentials saved.", "ok"); - if (creds.notionToken) { - $PI.sendToPlugin({ event: "refresh", notionToken: creds.notionToken }); - } } function populateUsers(users, savedUserId) { @@ -300,25 +296,14 @@ var payload = jsn.payload; if (payload.event === "updateStatus") { document.getElementById("updateStatus").textContent = payload.message; - if (payload.message && payload.message.indexOf("Updating") === 0) { - setTimeout(function() { location.reload(); }, 4000); - } } if (payload.event === "projects") { if (payload.version) { document.getElementById("versionText").textContent = "v" + payload.version; } - if (payload.users !== undefined) { + if (payload.users) { var savedUserId = document.getElementById("userId").value; populateUsers(payload.users, savedUserId); - var userErr = document.getElementById("userError"); - if (payload.usersError) { - userErr.textContent = "Could not load names: " + payload.usersError; - } else if (payload.users.length === 0) { - userErr.textContent = "No users found — check the integration has \u201cRead user information\u201d enabled."; - } else { - userErr.textContent = ""; - } } if (payload.error) { setStatus(payload.error, "error"); diff --git a/notion-timer.streamDeckPlugin b/notion-timer.streamDeckPlugin index ece814b..0956b61 100644 Binary files a/notion-timer.streamDeckPlugin and b/notion-timer.streamDeckPlugin differ diff --git a/src/plugin.ts b/src/plugin.ts index ba40d94..025416b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,20 +1,16 @@ -const CURRENT_VERSION = "1.0.22"; -const GITEA_BASE = "https://gitea.pdmarf.co.uk/pdm/stream_deck_notion_timer/raw/branch/master"; +const CURRENT_VERSION = "1.0.26"; +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= -----END PUBLIC KEY-----`; function isNewerVersion(remote: string, current: string): boolean { const parse = (v: string) => v.split(".").map(Number); - const r = parse(remote); - const c = parse(current); - const len = Math.max(r.length, c.length); - for (let i = 0; i < len; i++) { - const rv = r[i] ?? 0; - const cv = c[i] ?? 0; - if (rv !== cv) return rv > cv; - } - return false; + const [rMaj, rMin, rPat] = parse(remote); + const [cMaj, cMin, cPat] = parse(current); + if (rMaj !== cMaj) return rMaj > cMaj; + if (rMin !== cMin) return rMin > cMin; + return rPat > cPat; } function fetchWithTimeout(url: string): Promise { @@ -28,17 +24,16 @@ async function checkForUpdates(sendStatus?: (msg: string) => void): Promise void): Promise fetchWithTimeout(`${PLUGIN_BASE}/${p}`))); - fs.writeFileSync(__filename, newCode); - for (let i = 0; i < ASSETS.length; i++) { - if (!assetResps[i].ok) { streamDeck.logger.warn(`Asset download failed: ${ASSETS[i]}`); continue; } - fs.writeFileSync(path.join(pluginRoot, ASSETS[i]), Buffer.from(await assetResps[i].arrayBuffer())); - } - - // Remove legacy files that were replaced in older versions but persist on disk - // because Stream Deck merges rather than replaces the plugin folder on reinstall. - const LEGACY = ["imgs/idle.svg", "imgs/running.svg"]; - for (const f of LEGACY) { - try { fs.unlinkSync(path.join(pluginRoot, f)); } catch { /* already gone */ } - } - streamDeck.logger.info(`Updated to ${version}, restarting…`); process.exit(0); } catch (err) { @@ -139,30 +109,6 @@ async function setRunningEntry(entryId: string | null): Promise { await streamDeck.settings.setGlobalSettings({ ...stored, runningEntryId: entryId }); } -async function sendProjectsToPI(overrideToken?: string): Promise { - try { - const global = await getGlobal(); - const token = overrideToken || global.notionToken; - if (!token) { - await streamDeck.ui.sendToPropertyInspector({ event: "projects", data: [], error: "Enter your Notion API token above.", version: CURRENT_VERSION }); - return; - } - let usersResult: Awaited> = []; - let usersError: string | undefined; - const [projects] = await Promise.all([ - fetchProjects(token, global.projectsDbId), - fetchUsers(token).then((u) => { usersResult = u; }).catch((err) => { - streamDeck.logger.error("Failed to fetch users:", err); - usersError = err instanceof Error ? err.message : String(err); - }), - ]); - await streamDeck.ui.sendToPropertyInspector({ event: "projects", data: projects, users: usersResult, usersError, version: CURRENT_VERSION }); - } catch (err) { - streamDeck.logger.error("Failed to fetch projects:", err); - await streamDeck.ui.sendToPropertyInspector({ event: "projects", data: [], error: String(err), version: CURRENT_VERSION }); - } -} - function isConfigured(g: GlobalSettings): boolean { return !!(g.notionToken && g.userId); } @@ -200,8 +146,25 @@ class TimerToggle extends SingletonAction { } } - async onPropertyInspectorDidAppear(_ev: PropertyInspectorDidAppearEvent): Promise { - await sendProjectsToPI(); + async onPropertyInspectorDidAppear(ev: PropertyInspectorDidAppearEvent): Promise { + try { + const global = await getGlobal(); + if (!global.notionToken) { + await streamDeck.ui.sendToPropertyInspector({ event: "projects", data: [], error: "Enter your Notion API token above.", version: CURRENT_VERSION }); + return; + } + const [projects, usersResult] = await Promise.all([ + fetchProjects(global.notionToken, global.projectsDbId), + fetchUsers(global.notionToken).catch((err) => { + streamDeck.logger.error("Failed to fetch users:", err); + return []; + }), + ]); + await streamDeck.ui.sendToPropertyInspector({ event: "projects", data: projects, users: usersResult, version: CURRENT_VERSION }); + } catch (err) { + streamDeck.logger.error("Failed to fetch projects:", err); + await streamDeck.ui.sendToPropertyInspector({ event: "projects", data: [], error: String(err), version: CURRENT_VERSION }); + } } async onKeyDown(ev: KeyDownEvent): Promise { @@ -232,7 +195,19 @@ class TimerToggle extends SingletonAction { } else { const prevEntryId = await getRunningEntryId(); - // Stop previous timer + // Optimistically update visuals immediately — no waiting for API + if (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) { + 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); for (const other of this.actions) { @@ -242,8 +217,6 @@ class TimerToggle extends SingletonAction { 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 || "")); } } } @@ -259,8 +232,6 @@ class TimerToggle extends SingletonAction { 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}`); } } catch (err) { streamDeck.logger.error("Timer toggle failed:", err); @@ -279,9 +250,6 @@ streamDeck.ui.onSendToPlugin<{ event: string; settings?: TimerSettings }>(async const title = buttonTitle(ev.payload.settings.projectName || ""); if (title) await ev.action.setTitle(title); } - if (ev.payload.event === "refresh") { - await sendProjectsToPI(ev.payload.notionToken as string | undefined); - } if (ev.payload.event === "checkForUpdates") { const send = (msg: string) => streamDeck.ui.sendToPropertyInspector({ event: "updateStatus", message: msg }); send("Checking…");