diff --git a/client/client.ts b/client/client.ts index 701ae76a..09d11268 100644 --- a/client/client.ts +++ b/client/client.ts @@ -60,8 +60,8 @@ import { DataStore } from "./data/datastore.ts"; import { IndexedDBKvPrimitives } from "./data/indexeddb_kv_primitives.ts"; import { DataStoreMQ } from "./data/mq.datastore.ts"; -import { LimitedMap } from "@silverbulletmd/silverbullet/lib/limited_map"; import { fsEndpoint } from "./spaces/constants.ts"; +import { WidgetCache } from "./widget_cache.ts"; import { diffAndPrepareChanges } from "./codemirror/cm_util.ts"; import { DocumentEditor } from "./document_editor.ts"; import { parseExpressionString } from "./space_lua/parse.ts"; @@ -97,12 +97,6 @@ declare global { var client: Client; } -type WidgetCacheItem = { - html: string; - block?: boolean; - copyContent?: string; -}; - // TODO: Clean this up, this has become a god class... export class Client { // Event bus used to communicate between components @@ -149,18 +143,7 @@ export class Client { // Progress circle handling private progressTimeout?: ReturnType; // Widget and image height caching - private widgetCache = new LimitedMap(100); // bodyText -> WidgetCacheItem - debouncedWidgetCacheFlush = throttle(() => { - this.ds - .set(["cache", "widgets"], this.widgetCache.toJSON()) - .catch(console.error); - }, 2000); - private widgetHeightCache = new LimitedMap(1000); // bodytext -> height - debouncedWidgetHeightCacheFlush = throttle(() => { - this.ds - .set(["cache", "widgetHeight"], this.widgetHeightCache.toJSON()) - .catch(console.error); - }, 2000); + widgetCache!: WidgetCache; objectIndex!: ObjectIndex; constructor( @@ -205,6 +188,8 @@ export class Client { // Setup message queue on top of that this.mq = new DataStoreMQ(this.ds, this.eventHook); + this.widgetCache = new WidgetCache(this.ds); + this.objectIndex = new ObjectIndex( this.ds, this.config, @@ -255,7 +240,7 @@ export class Client { } } - await this.loadCaches(); + await this.widgetCache.load(); // Let's ping the remote space to ensure we're authenticated properly, if not will result in a redirect to auth page try { @@ -1312,33 +1297,6 @@ export class Client { return; } - async loadCaches() { - const [widgetHeightCache, widgetCache] = await this.ds.batchGet([ - ["cache", "widgetHeight"], - ["cache", "widgets"], - ]); - this.widgetHeightCache = new LimitedMap(1000, widgetHeightCache || {}); - this.widgetCache = new LimitedMap(100, widgetCache || {}); - } - - setCachedWidgetHeight(bodyText: string, height: number) { - this.widgetHeightCache.set(bodyText, height); - this.debouncedWidgetHeightCacheFlush(); - } - - getCachedWidgetHeight(bodyText: string): number { - return this.widgetHeightCache.get(bodyText) ?? -1; - } - - setWidgetCache(key: string, cacheItem: WidgetCacheItem) { - this.widgetCache.set(key, cacheItem); - this.debouncedWidgetCacheFlush(); - } - - getWidgetCache(key: string): WidgetCacheItem | undefined { - return this.widgetCache.get(key); - } - async handleServiceWorkerMessage(message: ServiceWorkerSourceMessage) { switch (message.type) { case "space-sync-complete": { diff --git a/client/codemirror/iframe_widget.ts b/client/codemirror/iframe_widget.ts index c1b75821..02c01027 100644 --- a/client/codemirror/iframe_widget.ts +++ b/client/codemirror/iframe_widget.ts @@ -20,7 +20,7 @@ export class IFrameWidget extends WidgetType { } override get estimatedHeight(): number { - const cachedHeight = this.client.getCachedWidgetHeight(this.bodyText); + const cachedHeight = this.client.widgetCache.getCachedWidgetHeight(this.bodyText); // console.log("Calling estimated height", this.bodyText, cachedHeight); return cachedHeight > 0 ? cachedHeight : 150; } diff --git a/client/codemirror/lua_widget.ts b/client/codemirror/lua_widget.ts index 1b1192b7..6ea99f8c 100644 --- a/client/codemirror/lua_widget.ts +++ b/client/codemirror/lua_widget.ts @@ -68,7 +68,7 @@ export class LuaWidget extends WidgetType { } override get estimatedHeight(): number { - return this.client.getCachedWidgetHeight(this.cacheKey); + return this.client.widgetCache.getCachedWidgetHeight(this.cacheKey); } toDOM(): HTMLElement { @@ -76,7 +76,7 @@ export class LuaWidget extends WidgetType { wrapperSpan.className = "sb-lua-wrapper"; const innerDiv = document.createElement("div"); wrapperSpan.appendChild(innerDiv); - const cacheItem = this.client.getWidgetCache(this.cacheKey); + const cacheItem = this.client.widgetCache.getWidgetCache(this.cacheKey); if (cacheItem) { if (cacheItem.block) { innerDiv.className += " sb-lua-directive-block"; @@ -126,8 +126,8 @@ export class LuaWidget extends WidgetType { if (widgetContent === null || widgetContent === undefined) { if (!this.renderEmpty) { div.innerHTML = ""; - this.client.setWidgetCache(this.cacheKey, { html: "", block: false }); - this.client.setCachedWidgetHeight(this.cacheKey, div.clientHeight); + this.client.widgetCache.setWidgetCache(this.cacheKey, { html: "", block: false }); + this.client.widgetCache.setCachedWidgetHeight(this.cacheKey, div.clientHeight); return; } widgetContent = { markdown: "nil", _isWidget: true }; @@ -195,8 +195,8 @@ export class LuaWidget extends WidgetType { if (!trimmedMarkdown) { // Net empty result after expansion div.innerHTML = ""; - this.client.setWidgetCache(this.cacheKey, { html: "", block: false }); - this.client.setCachedWidgetHeight(this.cacheKey, div.clientHeight); + this.client.widgetCache.setWidgetCache(this.cacheKey, { html: "", block: false }); + this.client.widgetCache.setCachedWidgetHeight(this.cacheKey, div.clientHeight); return; } @@ -245,12 +245,12 @@ export class LuaWidget extends WidgetType { // Let's give it a tick, then measure and cache setTimeout(() => { - this.client.setWidgetCache(this.cacheKey, { + this.client.widgetCache.setWidgetCache(this.cacheKey, { html: html?.outerHTML || "", block, copyContent: copyContent, }); - this.client.setCachedWidgetHeight(this.cacheKey, div.offsetHeight); + this.client.widgetCache.setCachedWidgetHeight(this.cacheKey, div.offsetHeight); // Because of the rejiggering of the DOM, we need to do a no-op cursor move to make sure it's positioned correctly this.client.editorView.dispatch({ selection: this.client.editorView.state.selection, diff --git a/client/codemirror/table.ts b/client/codemirror/table.ts index bfe15fa7..44920115 100644 --- a/client/codemirror/table.ts +++ b/client/codemirror/table.ts @@ -34,7 +34,7 @@ class TableViewWidget extends WidgetType { } override get estimatedHeight(): number { - return this.client.getCachedWidgetHeight(`table:${this.tableBodyText}`); + return this.client.widgetCache.getCachedWidgetHeight(`table:${this.tableBodyText}`); } toDOM(): HTMLElement { @@ -78,7 +78,7 @@ class TableViewWidget extends WidgetType { // Give it a tick to render attachWidgetEventHandlers(dom, this.client, this.tableBodyText); - this.client.setCachedWidgetHeight( + this.client.widgetCache.setCachedWidgetHeight( `table:${this.tableBodyText}`, dom.clientHeight, ); diff --git a/client/codemirror/top_bottom_panels.ts b/client/codemirror/top_bottom_panels.ts index e60e843c..d2f186b6 100644 --- a/client/codemirror/top_bottom_panels.ts +++ b/client/codemirror/top_bottom_panels.ts @@ -18,7 +18,7 @@ class ArrayWidget extends WidgetType { } override get estimatedHeight(): number { - return this.client.getCachedWidgetHeight(this.cacheKey); + return this.client.widgetCache.getCachedWidgetHeight(this.cacheKey); } toDOM(): HTMLElement { @@ -28,7 +28,7 @@ class ArrayWidget extends WidgetType { div.className = "sb-widget-array"; // This doesn't do that much, but it also doesn't really hurt - const cacheItem = this.client.getWidgetCache(this.cacheKey); + const cacheItem = this.client.widgetCache.getWidgetCache(this.cacheKey); if (cacheItem) { div.innerHTML = cacheItem.html; } @@ -91,11 +91,11 @@ class ArrayWidget extends WidgetType { // Wait for the clientHeight to settle setTimeout(() => { - this.client.setWidgetCache(this.cacheKey, { + this.client.widgetCache.setWidgetCache(this.cacheKey, { block: true, html: div.innerHTML, }); - this.client.setCachedWidgetHeight(this.cacheKey, div.clientHeight); + this.client.widgetCache.setCachedWidgetHeight(this.cacheKey, div.clientHeight); }); } diff --git a/client/components/widget_sandbox_iframe.ts b/client/components/widget_sandbox_iframe.ts index 64984b28..7e331dfd 100644 --- a/client/components/widget_sandbox_iframe.ts +++ b/client/components/widget_sandbox_iframe.ts @@ -157,7 +157,7 @@ export function mountIFrame( // iframe.height = data.height + "px"; iframe.style.height = `${data.height}px`; if (widgetHeightCacheKey) { - client.setCachedWidgetHeight(widgetHeightCacheKey, data.height); + client.widgetCache.setCachedWidgetHeight(widgetHeightCacheKey, data.height); } break; default: @@ -192,7 +192,7 @@ export function mountIFrame( if (resolvedContent.height) { iframe.style.height = `${resolvedContent.height}px`; if (widgetHeightCacheKey) { - client.setCachedWidgetHeight( + client.widgetCache.setCachedWidgetHeight( widgetHeightCacheKey!, resolvedContent.height, ); diff --git a/client/widget_cache.ts b/client/widget_cache.ts new file mode 100644 index 00000000..02c3f8ac --- /dev/null +++ b/client/widget_cache.ts @@ -0,0 +1,55 @@ +import { throttle } from "@silverbulletmd/silverbullet/lib/async"; +import { LimitedMap } from "@silverbulletmd/silverbullet/lib/limited_map"; +import type { DataStore } from "./data/datastore.ts"; + +export type WidgetCacheItem = { + html: string; + block?: boolean; + copyContent?: string; +}; + +export class WidgetCache { + private widgetCache = new LimitedMap(100); + private widgetHeightCache = new LimitedMap(1000); + + private debouncedWidgetCacheFlush = throttle(() => { + this.ds + .set(["cache", "widgets"], this.widgetCache.toJSON()) + .catch(console.error); + }, 2000); + + private debouncedWidgetHeightCacheFlush = throttle(() => { + this.ds + .set(["cache", "widgetHeight"], this.widgetHeightCache.toJSON()) + .catch(console.error); + }, 2000); + + constructor(private ds: DataStore) {} + + async load() { + const [widgetHeightCache, widgetCache] = await this.ds.batchGet([ + ["cache", "widgetHeight"], + ["cache", "widgets"], + ]); + this.widgetHeightCache = new LimitedMap(1000, widgetHeightCache || {}); + this.widgetCache = new LimitedMap(100, widgetCache || {}); + } + + setCachedWidgetHeight(bodyText: string, height: number) { + this.widgetHeightCache.set(bodyText, height); + this.debouncedWidgetHeightCacheFlush(); + } + + getCachedWidgetHeight(bodyText: string): number { + return this.widgetHeightCache.get(bodyText) ?? -1; + } + + setWidgetCache(key: string, cacheItem: WidgetCacheItem) { + this.widgetCache.set(key, cacheItem); + this.debouncedWidgetCacheFlush(); + } + + getWidgetCache(key: string): WidgetCacheItem | undefined { + return this.widgetCache.get(key); + } +}