From ba98ed6412ab9335d2df4f6d41f60eb1c572e2a3 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 3 Feb 2023 11:52:51 +1100 Subject: [PATCH] chore: refactor core/client.mjs, sync telemetry opt-in/out across profiles --- src/api/electron.cjs | 2 +- src/core/client.mjs | 280 +++++++++++----------------- src/core/components.mjs | 95 ++++++++++ src/core/menu/components/Popup.mjs | 68 +++---- src/core/menu/islands/Telemetry.mjs | 9 +- 5 files changed, 246 insertions(+), 208 deletions(-) create mode 100644 src/core/components.mjs diff --git a/src/api/electron.cjs b/src/api/electron.cjs index cabd997..722ab34 100644 --- a/src/api/electron.cjs +++ b/src/api/electron.cjs @@ -72,10 +72,10 @@ const initDatabase = (namespace, fallbacks = {}) => { // schema: // - ("agreedToTerms") -> boolean + // - ("telemetryEnabled") -> boolean // - ("profileIds") -> $profileId[] // - ("activeProfile") -> $profileId // - $profileId: ("profileName") -> string - // - $profileId: ("telemetryEnabled") -> boolean // - $profileId__enabledMods: ($modId) -> boolean // - $profileId__$modId: ($optionKey) -> value diff --git a/src/core/client.mjs b/src/core/client.mjs index bd4542e..dc8d0a8 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -5,186 +5,128 @@ */ import { checkForUpdate } from "./update.mjs"; +import { Frame, Modal, Button } from "./components.mjs"; -const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`; +// prettier-ignore +const asyncFilter = async (arr, predicate) => Promise.all(arr.map(predicate)) + .then((results) => arr.filter((_v, index) => results[index])); -function SidebarButton( - { icon, notifications, themeOverridesLoaded, ...props }, - ...children -) { - const { html } = globalThis.__enhancerApi; - return html`
-
- -
-
${children}
- -
- -
- ${notifications} -
-
-
`; -} - -export default async (api, db) => { - const { - html, - platform, - version, - getMods, - isEnabled, - enhancerUrl, - onMessage, - sendMessage, - addMutationListener, - addKeyListener, - initDatabase, - } = api, - openMenuHotkey = await db.get("openMenuHotkey"), - menuButtonIconStyle = await db.get("menuButtonIconStyle"), - loadThemeOverrides = await db.get("loadThemeOverrides"), - customStyles = (await db.get("customStyles"))?.content; - - // appearance - - const enabledThemes = (await getMods("themes")).map((theme) => - isEnabled(theme.id) - ), - forceLoadOverrides = loadThemeOverrides === "Enabled", - autoLoadOverrides = - loadThemeOverrides === "Auto" && - (await Promise.all(enabledThemes)).some((enabled) => enabled); - if (forceLoadOverrides || autoLoadOverrides) { +const doThemeOverride = async (db) => { + const { getMods, isEnabled } = globalThis.__enhancerApi, + enabledFilter = (theme) => isEnabled(theme.id), + overrideThemes = await db.get("loadThemeOverrides"), + enabledThemes = await asyncFilter(await getMods("themes"), enabledFilter); + return ( + overrideThemes === "Enabled" || + (overrideThemes === "Auto" && enabledThemes.length) + ); + }, + overrideThemes = async (db) => { + const { html, enhancerUrl } = globalThis.__enhancerApi; + if (!(await doThemeOverride(db))) return; document.head.append(html``); - } - - if (customStyles) { - const $customStyles = html``; - document.head.append($customStyles); - } - - // menu - - let $menuModal, $menuFrame, _notionTheme; - const updateTheme = (force = false) => { - const darkMode = document.body.classList.contains("dark"), - notionTheme = darkMode ? "dark" : "light"; - if (notionTheme !== _notionTheme || force) { - _notionTheme = notionTheme; - const msg = { - namespace: "notion-enhancer", - hotkey: openMenuHotkey, - theme: notionTheme, - icon: menuButtonIconStyle, - }; - $menuFrame?.contentWindow.postMessage(msg, "*"); - } + `); }; - const openMenu = () => { - updateTheme(true); - $menuModal?.setAttribute("open", true); - $menuFrame?.contentWindow.focus(); - }, - closeMenu = () => $menuModal?.removeAttribute("open"); +const insertMenu = async (db) => { + const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`, + { html, addKeyListener, addMutationListener } = globalThis.__enhancerApi, + { platform, enhancerUrl, onMessage } = globalThis.__enhancerApi, + menuButtonIconStyle = await db.get("menuButtonIconStyle"), + openMenuHotkey = await db.get("openMenuHotkey"), + renderPing = { + namespace: "notion-enhancer", + hotkey: openMenuHotkey, + icon: menuButtonIconStyle, + }; - $menuFrame = html``; - $menuModal = html`
-
-
- ${$menuFrame} -
-
`; - document.body.append($menuModal); + let _contentWindow; + const sendThemePing = () => { + if (!_contentWindow) return; + const darkMode = document.body.classList.contains("dark"), + notionTheme = darkMode ? "dark" : "light"; + if (renderPing.theme === notionTheme) return; + renderPing.theme = notionTheme; + _contentWindow.postMessage(renderPing, "*"); + }, + sendRenderPing = (contentWindow) => { + _contentWindow ??= contentWindow; + if (!$modal.hasAttribute("open")) return; + delete renderPing.theme; + _contentWindow.focus(); + sendThemePing(); + }; - const $menuButton = html`<${SidebarButton} - onclick=${openMenu} - notifications=${(await checkForUpdate()) ? 1 : 0} - icon="notion-enhancer${menuButtonIconStyle === "Monochrome" - ? "?mask" - : " text-[16px]"}" - >notion-enhancer - `; - addMutationListener(notionSidebar, () => { - if (document.contains($menuButton)) return; - document.querySelector(notionSidebar)?.append($menuButton); - }); - document.querySelector(notionSidebar)?.append($menuButton); + const $modal = html`<${Modal} onopen=${sendRenderPing}> + <${Frame} + title="notion-enhancer menu" + src="${enhancerUrl("core/menu/index.html")}" + onload=${function () { + // pass notion-enhancer api to electron menu process + if (platform !== "browser") { + const apiKey = "__enhancerApi"; + this.contentWindow[apiKey] = globalThis[apiKey]; + } + sendRenderPing(this.contentWindow); + }} + /> + `, + $button = html`<${Button} + onclick=${$modal.open} + notifications=${(await checkForUpdate()) ? 1 : 0} + themeOverridesLoaded=${await doThemeOverride(db)} + icon="notion-enhancer${menuButtonIconStyle === "Monochrome" + ? "?mask" + : " text-[16px]"}" + >notion-enhancer + `; + document.body.append($modal); + addMutationListener(notionSidebar, () => { + if (document.contains($button)) return; + document.querySelector(notionSidebar)?.append($button); + }); + document.querySelector(notionSidebar)?.append($button); + addMutationListener("body", sendThemePing); + window.addEventListener("focus", sendRenderPing); - window.addEventListener("focus", () => updateTheme(true)); - window.addEventListener("message", (event) => { - if (event.data?.namespace !== "notion-enhancer") return; - if (event.data?.action === "close-menu") closeMenu(); - if (event.data?.action === "open-menu") openMenu(); - }); - addMutationListener("body", () => { - if ($menuModal?.hasAttribute("open")) updateTheme(); - }); - onMessage("notion-enhancer", (message) => { - if (message === "open-menu") openMenu(); - }); - addKeyListener(openMenuHotkey, (event) => { - event.preventDefault(); - openMenu(); - }); - addKeyListener("Escape", () => { - if (document.activeElement?.nodeName === "INPUT") return; - closeMenu(); - }); - - sendMessage("notion-enhancer", "load-complete"); - - if ((await initDatabase().get("agreedToTerms")) === version) { + addKeyListener(openMenuHotkey, (event) => { + event.preventDefault(); + $modal.open(); + }); + window.addEventListener("message", (event) => { + if (event.data?.namespace !== "notion-enhancer") return; + if (event.data?.action === "close-menu") $modal.close(); + if (event.data?.action === "open-menu") $modal.open(); + }); + onMessage("notion-enhancer", (message) => { + if (message === "open-menu") $modal.open(); + }); + }, + sendTelemetryPing = async () => { + const { version } = globalThis.__enhancerApi, + db = globalThis.__enhancerApi.initDatabase(), + agreedToTerms = await db.get("agreedToTerms"), + telemetryEnabled = await db.get("telemetryEnabled"); + if (!telemetryEnabled || agreedToTerms !== version) return; // telemetry - } + }; + +export default async (api, db) => { + await Promise.all([ + overrideThemes(db), + insertCustomStyles(db), + insertMenu(db), + sendTelemetryPing(), + ]); + api.sendMessage("notion-enhancer", "load-complete"); }; diff --git a/src/core/components.mjs b/src/core/components.mjs new file mode 100644 index 0000000..2d406e2 --- /dev/null +++ b/src/core/components.mjs @@ -0,0 +1,95 @@ +/** + * notion-enhancer + * (c) 2023 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +function Frame(props) { + const { html, extendProps } = globalThis.__enhancerApi; + extendProps(props, { + class: `rounded-[5px] w-[1150px] h-[calc(100vh-100px)] + max-w-[calc(100vw-100px)] max-h-[715px] overflow-hidden + bg-[color:var(--theme--bg-primary)] drop-shadow-xl + group-open:(pointer-events-auto opacity-100 scale-100) + transition opacity-0 scale-95`, + }); + return html``; +} + +function Modal(props, ...children) { + const { html, extendProps, addKeyListener } = globalThis.__enhancerApi; + extendProps(props, { + class: `notion-enhancer--menu-modal group + z-[999] fixed inset-0 w-screen h-screen + transition pointer-events-none opacity-0 + open:(pointer-events-auto opacity-100)`, + }); + const $modal = html`
+
$modal.close()} + >
+
+ ${children} +
+
`; + $modal.open = () => { + $modal.setAttribute("open", ""); + $modal.onopen?.(); + }; + $modal.close = () => { + $modal.onbeforeclose?.(); + $modal.removeAttribute("open"); + $modal.style.pointerEvents = "auto"; + setTimeout(() => { + $modal.style.pointerEvents = ""; + $modal.onclose?.(); + }, 200); + }; + addKeyListener("Escape", () => { + if (document.activeElement?.nodeName === "INPUT") return; + $modal.close(); + }); + return $modal; +} + +function Button( + { icon, notifications, themeOverridesLoaded, ...props }, + ...children +) { + const { html, extendProps } = globalThis.__enhancerApi; + extendProps(props, { + tabindex: 0, + role: "button", + class: `notion-enhancer--menu-button + flex select-none cursor-pointer rounded-[3px] + text-[14px] my-px mx-[4px] py-[2px] px-[10px] + transition hover:bg-[color:var(--theme--bg-hover)]`, + }); + return html`
+
+ +
+
${children}
+ +
+ +
+ ${notifications} +
+
+
`; +} + +export { Frame, Modal, Button }; diff --git a/src/core/menu/components/Popup.mjs b/src/core/menu/components/Popup.mjs index d6d74ea..815365b 100644 --- a/src/core/menu/components/Popup.mjs +++ b/src/core/menu/components/Popup.mjs @@ -6,57 +6,47 @@ import { setState, useState } from "../state.mjs"; -function Popup({ trigger, onopen, onclose, onbeforeclose }, ...children) { - const { html, extendProps } = globalThis.__enhancerApi, - $popup = html`
-
-
- ${children} -
-
-
`; +function Popup({ trigger, ...props }, ...children) { + const { html, extendProps } = globalThis.__enhancerApi; + extendProps(props, { + class: `notion-enhancer--menu-popup + group absolute top-0 left-0 w-full h-full + flex-(& col) justify-center items-end z-20 + pointer-events-none font-normal text-left`, + }); + const $popup = html`
+
+
+ ${children} +
+
+
`; $popup.show = () => { $popup.setAttribute("open", true); $popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0)); setState({ popupOpen: true }); - onopen?.(); + $popup.onopen?.(); }; $popup.hide = () => { - onbeforeclose?.(); + $popup.onbeforeclose?.(); $popup.removeAttribute("open"); $popup.style.pointerEvents = "auto"; $popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1)); setTimeout(() => { $popup.style.pointerEvents = ""; setState({ popupOpen: false }); - onclose?.(); + $popup.onclose?.(); }, 200); }; $popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1)); - if (trigger) { - extendProps(trigger, { - onclick: $popup.show, - onkeydown(event) { - if ([" ", "Enter"].includes(event.key)) { - event.preventDefault(); - $popup.show(); - } - }, - }); - } document.addEventListener("click", (event) => { if (!$popup.hasAttribute("open")) return; if ($popup.contains(event.target) || $popup === event.target) return; @@ -67,6 +57,16 @@ function Popup({ trigger, onopen, onclose, onbeforeclose }, ...children) { if ($popup.hasAttribute("open")) $popup.hide(); }); + if (!trigger) return $popup; + extendProps(trigger, { + onclick: $popup.show, + onkeydown(event) { + if ([" ", "Enter"].includes(event.key)) { + event.preventDefault(); + $popup.show(); + } + }, + }); return $popup; } diff --git a/src/core/menu/islands/Telemetry.mjs b/src/core/menu/islands/Telemetry.mjs index a7c7340..c752aa1 100644 --- a/src/core/menu/islands/Telemetry.mjs +++ b/src/core/menu/islands/Telemetry.mjs @@ -25,10 +25,10 @@ function Telemetry() { const _get = async () => { // defaults to true, must be explicitly set to false to disable - return initDatabase([await getProfile()]).get("telemetryEnabled") ?? true; + return initDatabase().get("telemetryEnabled") ?? true; }, _set = async (value) => { - await initDatabase([await getProfile()]).set("telemetryEnabled", value); + await initDatabase().set("telemetryEnabled", value); setState({ rerender: true, databaseUpdated: true }); }; @@ -42,8 +42,9 @@ function Telemetry() { platform ("${platform}"), timezone ("${timezone}"), notion-enhancer version ("${version}"), and enabled mods (${$enabledMods}). You can - opt in or out of telemetry at any time. For more information, read the - notion-enhancer's privacy policy.`} + opt in or out of telemetry at any time. This setting syncs across + configuration profiles. For more information, read the notion-enhancer's + privacy policy.`} ...${{ _get, _set }} />`; }