From 463607c6d2852e1ac370709069f11b50054d2487 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 15 Aug 2023 23:55:43 +1000 Subject: [PATCH] feat(panel): register/render custom views to side panel + fix: debounce useState callback triggers to prevent over-handling conflicts + minor refactoring --- src/core/client.mjs | 15 +- src/core/islands/Panel.mjs | 251 ++++++++++++++++++------------- src/core/menu/islands/Select.mjs | 26 ++-- src/shared/events.js | 58 ++++++- src/shared/markup.js | 34 +---- src/shared/registry.js | 7 +- 6 files changed, 232 insertions(+), 159 deletions(-) diff --git a/src/core/client.mjs b/src/core/client.mjs index 9899313..707151b 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -123,13 +123,18 @@ const insertPanel = async (api, db) => { const notionFrame = ".notion-frame", notionTopbarBtn = ".notion-topbar-more-button", togglePanelHotkey = await db.get("togglePanelHotkey"), - { html } = api; + { html, setState, addPanelView } = api; const $panel = html`<${Panel} - _getWidth=${() => db.get("sidePanelWidth")} - _setWidth=${(width) => db.set("sidePanelWidth", width)} - _getOpen=${() => db.get("sidePanelOpen")} - _setOpen=${(open) => db.set("sidePanelOpen", open)} + ...${Object.assign( + ...["Width", "Open", "View"].map((key) => ({ + [`_get${key}`]: () => db.get(`sidePanel${key}`), + [`_set${key}`]: async (value) => { + await db.set(`sidePanel${key}`, value); + setState({ rerender: true }); + }, + })) + )} />`, togglePanel = () => { if ($panel.hasAttribute("open")) $panel.close(); diff --git a/src/core/islands/Panel.mjs b/src/core/islands/Panel.mjs index fc00eb3..8142976 100644 --- a/src/core/islands/Panel.mjs +++ b/src/core/islands/Panel.mjs @@ -7,9 +7,73 @@ import { Tooltip } from "./Tooltip.mjs"; import { Select } from "../menu/islands/Select.mjs"; -function View(props) { - const { html } = globalThis.__enhancerApi; - return html``; +// note: these islands do not accept extensible +// properties, i.e. they are not reusable. +// please register your own interfaces via +// globalThis.__enhancerApi.addPanelView and +// not by re-instantiating additional panels + +let panelViews = [], + // "$icon" may either be an actual dom element, + // or an icon name from the lucide icons set + addPanelView = ({ title, $icon, $view }) => { + panelViews.push([{ title, $icon }, $view]); + panelViews.sort(([{ title: a }], [{ title: b }]) => a.localeCompare(b)); + const { setState } = globalThis.__enhancerApi; + setState?.({ panelViews }); + }, + removePanelView = ($view) => { + panelViews = panelViews.filter(([, v]) => v !== $view); + const { setState } = globalThis.__enhancerApi; + setState?.({ panelViews }); + }; + +function View({ _get }) { + const { html, setState, useState } = globalThis.__enhancerApi, + $container = html`
`; + useState(["rerender"], async () => { + const openView = await _get?.(), + $view = + panelViews.find(([{ title }]) => { + return title === openView; + })?.[1] || panelViews[0]?.[1]; + if (!$container.contains($view)) { + $container.innerHTML = ""; + $container.append($view); + } + }); + return $container; +} + +function Switcher({ _get, _set, minWidth, maxWidth }) { + const { html, extendProps, setState, useState } = globalThis.__enhancerApi, + $switcher = html`
`, + setView = (view) => { + _set?.(view); + setState({ activePanelView: view }); + }; + useState(["panelViews"], ([panelViews]) => { + const values = panelViews.map(([{ title, $icon }]) => { + // panel switcher internally uses the select island, + // which expects an option value rather than a title + return { value: title, $icon }; + }); + $switcher.innerHTML = ""; + $switcher.append(html`<${Select} + popupMode="dropdown" + class="w-full text-left" + maxWidth=${maxWidth - 56} + minWidth=${minWidth - 56} + ...${{ _get, _set: setView, values }} + />`); + }); + return $switcher; } function Panel({ @@ -17,91 +81,77 @@ function Panel({ _setWidth, _getOpen, _setOpen, + _getView, + _setView, minWidth = 250, maxWidth = 640, transitionDuration = 300, - ...props }) { - const { html, extendProps, setState, useState } = globalThis.__enhancerApi, - { addMutationListener, removeMutationListener } = globalThis.__enhancerApi; - extendProps(props, { - class: `notion-enhancer--panel order-2 shrink-0 - transition-[width] open:w-[var(--panel--width)] - border-l-1 border-[color:var(--theme--fg-border)] - relative bg-[color:var(--theme--bg-primary)] w-0 - duration-[${transitionDuration}ms] group/panel`, - }); + const { html, setState, useState } = globalThis.__enhancerApi, + { addMutationListener, removeMutationListener } = globalThis.__enhancerApi, + $panel = html``; - const values = [ - { - icon: "type", - value: "word counter", - }, - { - // prettier-ignore - $icon: html` - - - - - - - `, - value: "outliner", - }, - ], - _get = () => useState(["panelView"])[0], - _set = (value) => { - setState({ panelView: value, rerender: true }); - }; - - const $resize = html`
`, - $close = html`