From 73561dc12dffa11417aa42e11c00d939ebcca994 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 26 Dec 2024 11:09:48 -0800 Subject: [PATCH] new utility fn, deepCompareReturnPrev. use for settings prefix atom to do a deep equal (#1630) --- frontend/app/app.tsx | 4 ++-- frontend/app/store/global.ts | 17 ++++++++--------- frontend/app/view/term/term.tsx | 4 ++-- frontend/util/util.ts | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index f2bb79b09..3859a2168 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -6,11 +6,11 @@ import { ContextMenuModel } from "@/store/contextmenu"; import { atoms, createBlock, + getSettingsPrefixAtom, globalStore, isDev, PLATFORM, removeFlashError, - useSettingsPrefixAtom, } from "@/store/global"; import { appHandleKeyDown } from "@/store/keymodel"; import { getElemAsStr } from "@/util/focusutil"; @@ -123,7 +123,7 @@ async function handleContextMenu(e: React.MouseEvent) { } function AppSettingsUpdater() { - const windowSettingsAtom = useSettingsPrefixAtom("window"); + const windowSettingsAtom = getSettingsPrefixAtom("window"); const windowSettings = useAtomValue(windowSettingsAtom); useEffect(() => { const isTransparentOrBlur = diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index cbd56ec52..4ecc084ae 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -10,7 +10,7 @@ import { import { getLayoutModelForStaticTab } from "@/layout/lib/layoutModelHooks"; import { getWebServerEndpoint } from "@/util/endpoints"; import { fetch } from "@/util/fetchutil"; -import { getPrefixedSettings, isBlank } from "@/util/util"; +import { deepCompareReturnPrev, getPrefixedSettings, isBlank } from "@/util/util"; import { atom, Atom, PrimitiveAtom, useAtomValue } from "jotai"; import { globalStore } from "./jotaiStore"; import { modalsModel } from "./modalmodel"; @@ -314,16 +314,15 @@ function useSettingsKeyAtom(key: T): SettingsType[ return useAtomValue(getSettingsKeyAtom(key)); } -function useSettingsPrefixAtom(prefix: string): Atom { - // TODO: use a shallow equal here to make this more efficient - let settingsPrefixAtom = settingsAtomCache.get(prefix + ":") as Atom; +function getSettingsPrefixAtom(prefix: string): Atom { + let settingsPrefixAtom = settingsAtomCache.get(prefix + ":"); if (settingsPrefixAtom == null) { + // create a stable, closured reference to use as the deepCompareReturnPrev key + const cacheKey = {}; settingsPrefixAtom = atom((get) => { const settings = get(atoms.settingsAtom); - if (settings == null) { - return {}; - } - return getPrefixedSettings(settings, prefix); + const newValue = getPrefixedSettings(settings, prefix); + return deepCompareReturnPrev(cacheKey, newValue); }); settingsAtomCache.set(prefix + ":", settingsPrefixAtom); } @@ -674,6 +673,7 @@ export { getObjectId, getOverrideConfigAtom, getSettingsKeyAtom, + getSettingsPrefixAtom, getUserName, globalStore, initGlobal, @@ -700,6 +700,5 @@ export { useBlockMetaKeyAtom, useOverrideConfigAtom, useSettingsKeyAtom, - useSettingsPrefixAtom, WOS, }; diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 4ab7996d7..6fb3a6e70 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -17,9 +17,9 @@ import { getConnStatusAtom, getOverrideConfigAtom, getSettingsKeyAtom, + getSettingsPrefixAtom, globalStore, useBlockAtom, - useSettingsPrefixAtom, WOS, } from "@/store/global"; import * as services from "@/store/services"; @@ -773,7 +773,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { const viewRef = React.useRef(null); const connectElemRef = React.useRef(null); const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); - const termSettingsAtom = useSettingsPrefixAtom("term"); + const termSettingsAtom = getSettingsPrefixAtom("term"); const termSettings = jotai.useAtomValue(termSettingsAtom); let termMode = blockData?.meta?.["term:mode"] ?? "term"; if (termMode != "term" && termMode != "vdom") { diff --git a/frontend/util/util.ts b/frontend/util/util.ts index 9af85bc26..457b00df4 100644 --- a/frontend/util/util.ts +++ b/frontend/util/util.ts @@ -5,6 +5,7 @@ import base64 from "base64-js"; import clsx from "clsx"; import { Atom, atom, Getter, SetStateAction, Setter, useAtomValue } from "jotai"; import { debounce, throttle } from "throttle-debounce"; +const prevValueCache = new WeakMap(); // stores a previous value for a deep equal comparison (used with the deepCompareReturnPrev function) function isBlank(str: string): boolean { return str == null || str == ""; @@ -42,6 +43,20 @@ function boundNumber(num: number, min: number, max: number): number { return Math.min(Math.max(num, min), max); } +// key must be a suitable weakmap key. pass the new value +// it will return the prevValue (for object equality) if the new value is deep equal to the prev value +function deepCompareReturnPrev(key: any, newValue: any): any { + if (key == null) { + return newValue; + } + const previousValue = prevValueCache.get(key); + if (previousValue !== undefined && JSON.stringify(newValue) === JSON.stringify(previousValue)) { + return previousValue; + } + prevValueCache.set(key, newValue); + return newValue; +} + // works for json-like objects (arrays, objects, strings, numbers, booleans) function jsonDeepEqual(v1: any, v2: any): boolean { if (v1 === v2) { @@ -294,6 +309,7 @@ export { base64ToString, boundNumber, countGraphemes, + deepCompareReturnPrev, fireAndForget, getPrefixedSettings, getPromiseState,