From c95d96cd8e218f296441d94790aaf0361fcaa9c4 Mon Sep 17 00:00:00 2001
From: dragonwocky
Date: Sat, 14 Jan 2023 21:38:51 +1100
Subject: [PATCH] feat(menu): option values update on view change/page focus,
animate view changes
---
src/api/electron.cjs | 7 +-
src/core/client.mjs | 8 +-
src/core/menu/components.mjs | 309 ++++++++++++++++++-------------
src/core/menu/menu.mjs | 193 +++++++++++--------
src/core/menu/state.mjs | 7 +-
src/core/mod.json | 18 ++
src/themes/classic-dark/mod.json | 45 +++++
7 files changed, 371 insertions(+), 216 deletions(-)
diff --git a/src/api/electron.cjs b/src/api/electron.cjs
index bd32d36..b9efeec 100644
--- a/src/api/electron.cjs
+++ b/src/api/electron.cjs
@@ -78,10 +78,15 @@ const initDatabase = (namespace, fallbacks = {}) => {
get: (key) => {
const fallback = fallbacks[key];
key = key.startsWith(namespace) ? key : namespace + key;
- return select.get(key)?.value ?? fallback;
+ try {
+ return JSON.parse(select.get(key)?.value);
+ } catch {
+ return select.get(key)?.value ?? fallback;
+ }
},
set: (key, value) => {
key = key.startsWith(namespace) ? key : namespace + key;
+ value = JSON.stringify(value);
return select.get(key) === undefined
? insert.run(key, value)
: update.run(value, key);
diff --git a/src/core/client.mjs b/src/core/client.mjs
index db1d269..738f1cc 100644
--- a/src/core/client.mjs
+++ b/src/core/client.mjs
@@ -21,7 +21,7 @@ export default async (api, db) => {
openMenuHotkey = await db.get("openMenuHotkey"),
menuButtonIconStyle = await db.get("menuButtonIconStyle"),
loadThemeOverrides = await db.get("loadThemeOverrides"),
- customStyles = JSON.parse((await db.get("customStyles")) || "{}").content;
+ customStyles = (await db.get("customStyles"))?.content;
// appearance
@@ -54,8 +54,8 @@ export default async (api, db) => {
_notionTheme = notionTheme;
const msg = {
namespace: "notion-enhancer",
- iconStyle: menuButtonIconStyle,
- mode: notionTheme,
+ theme: notionTheme,
+ icon: menuButtonIconStyle,
};
$menuFrame?.contentWindow.postMessage(msg, "*");
}
@@ -64,6 +64,7 @@ export default async (api, db) => {
const openMenu = () => {
updateTheme(true);
$menuModal?.setAttribute("open", true);
+ $menuFrame?.contentWindow.focus();
},
closeMenu = () => $menuModal?.removeAttribute("open");
@@ -129,6 +130,7 @@ export default async (api, db) => {
});
document.querySelector(notionSidebar)?.append($menuButton);
+ window.addEventListener("focus", () => updateTheme(true));
addMutationListener("body", () => {
if ($menuModal?.hasAttribute("open")) updateTheme();
});
diff --git a/src/core/menu/components.mjs b/src/core/menu/components.mjs
index bfdfcdb..ccc6a12 100644
--- a/src/core/menu/components.mjs
+++ b/src/core/menu/components.mjs
@@ -4,7 +4,7 @@
* (https://notion-enhancer.github.io/) under the MIT license
*/
-import { setState, useState } from "./state.mjs";
+import { setState, useState, getState } from "./state.mjs";
function Sidebar({}, ...children) {
const { html } = globalThis.__enhancerApi;
@@ -43,7 +43,7 @@ function SidebarButton({ icon, ...props }, ...children) {
/>`;
if (!props.href) {
const id = $el.innerText;
- $el.onclick ??= () => setState({ view: id });
+ $el.onclick ??= () => setState({ transition: "fade", view: id });
useState(["view"], ([view = "welcome"]) => {
const active = view.toLowerCase() === id.toLowerCase();
$el.style.background = active ? "var(--theme--bg-hover)" : "";
@@ -55,16 +55,34 @@ function SidebarButton({ icon, ...props }, ...children) {
function View({ id }, ...children) {
const { html } = globalThis.__enhancerApi,
+ duration = 100,
$el = html``;
useState(["view"], ([view = "welcome"]) => {
- const active = view.toLowerCase() === id.toLowerCase();
- $el.style.display = active ? "" : "none";
+ const [transition] = getState(["transition"]),
+ isVisible = $el.style.display !== "none",
+ nowActive = view.toLowerCase() === id.toLowerCase();
+ if (transition === "fade") {
+ $el.style.opacity = "0";
+ $el.style.transition = `opacity ${duration}ms`;
+ if (isVisible && !nowActive) {
+ setTimeout(() => ($el.style.display = "none"), duration);
+ } else if (!isVisible && nowActive) {
+ setTimeout(() => {
+ $el.style.display = "";
+ requestIdleCallback(() => ($el.style.opacity = "1"));
+ }, duration);
+ }
+ } else {
+ $el.style.transition = "";
+ $el.style.opacity = nowActive ? "1" : "0";
+ $el.style.display = nowActive ? "" : "none";
+ }
});
return $el;
}
@@ -83,11 +101,12 @@ function Mod({
tags = [],
authors,
options = [],
- enabled,
- _update,
+ _get,
+ _set,
_src,
}) {
const { html, enhancerUrl } = globalThis.__enhancerApi,
+ toggleId = Math.random().toString(36).slice(2, 5),
$thumbnail = thumbnail
? html`
{
- // open mod options page
- }}
+ onclick=${() => setState({ transition: "none", view: id })}
>
`
: "";
return html`
-
+
${authors.map((author) => {
return html`
@@ -145,25 +161,20 @@ function Mod({
${$options}
- <${Toggle}
- id="${id}-toggle"
- checked=${enabled}
- onchange="${(event) => _update(event.target.checked)}"
- />
+ <${Toggle} id=${toggleId} ...${{ _get, _set }} />
`;
}
-function Option({ type, value, description, _update, ...props }) {
+function Option({ type, value, description, _get, _set, ...props }) {
const { html } = globalThis.__enhancerApi,
camelToSentenceCase = (string) =>
string[0].toUpperCase() +
string.replace(/[A-Z]/g, (match) => ` ${match.toLowerCase()}`).slice(1);
let $input;
- const label = props.label ?? camelToSentenceCase(props.key),
- onchange = (event) => _update(event.target.value);
+ const label = props.label ?? camelToSentenceCase(props.key);
switch (type) {
case "heading":
return html``;
case "text":
- $input = html`<${TextInput} ...${{ value, onchange }} />`;
+ $input = html`<${TextInput} ...${{ _get, _set }} />`;
break;
case "number":
- $input = html`<${NumberInput} ...${{ value, onchange }} />`;
+ $input = html`<${NumberInput} ...${{ _get, _set }} />`;
break;
case "hotkey":
- $input = html`<${HotkeyInput} ...${{ value, onchange }} />`;
+ $input = html`<${HotkeyInput} ...${{ _get, _set }} />`;
break;
case "color":
- $input = html`<${ColorInput} ...${{ value, onchange }} />`;
+ $input = html`<${ColorInput} ...${{ _get, _set }} />`;
break;
case "file":
- try {
- value = JSON.parse(value);
- } catch {
- value = {};
- }
$input = html`<${FileInput}
- filename="${value.filename}"
extensions="${props.extensions}"
- onupload=${(upload) => _update(JSON.stringify(upload))}
+ ...${{ _get, _set }}
/>`;
break;
case "select":
- $input = html`<${Select}
- values=${props.values}
- ...${{ value, onchange }}
- />`;
+ $input = html`<${Select} values=${props.values} ...${{ _get, _set }} />`;
break;
case "toggle":
- $input = html`<${Toggle}
- checked=${value}
- onchange=${(event) => _update(event.target.checked)}
- />`;
+ $input = html`<${Toggle} ...${{ _get, _set }} />`;
}
return html`<${type === "toggle" ? "label" : "div"}
class="notion-enhancer--menu-option flex items-center justify-between
@@ -225,19 +224,28 @@ function Option({ type, value, description, _update, ...props }) {
/>`;
}
-function TextInput({ value, ...props }) {
- const { html } = globalThis.__enhancerApi;
- return html`