Refactor: extract widget cache from Client class

This commit is contained in:
Zef Hemel
2026-03-10 12:17:02 +01:00
parent 822997fd5e
commit cb9f954bc7
7 changed files with 77 additions and 64 deletions
+5 -47
View File
@@ -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<typeof setTimeout>;
// Widget and image height caching
private widgetCache = new LimitedMap<WidgetCacheItem>(100); // bodyText -> WidgetCacheItem
debouncedWidgetCacheFlush = throttle(() => {
this.ds
.set(["cache", "widgets"], this.widgetCache.toJSON())
.catch(console.error);
}, 2000);
private widgetHeightCache = new LimitedMap<number>(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": {
+1 -1
View File
@@ -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;
}
+8 -8
View File
@@ -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,
+2 -2
View File
@@ -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,
);
+4 -4
View File
@@ -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);
});
}
+2 -2
View File
@@ -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,
);
+55
View File
@@ -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<WidgetCacheItem>(100);
private widgetHeightCache = new LimitedMap<number>(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);
}
}