Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 27 additions & 22 deletions frontend/layout/lib/layoutModelHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,18 @@ import { LayoutModel } from "./layoutModel";
import { LayoutNode, NodeModel, TileLayoutContents } from "./types";

const layoutModelMap: Map<string, LayoutModel> = new Map();
const timeoutMap: Map<string, NodeJS.Timeout | null> = new Map();

export function getLayoutModelForTab(tabAtom: Atom<Tab>): LayoutModel {
const tabData = globalStore.get(tabAtom);
if (!tabData) return;
const tabId = tabData.oid;
if (layoutModelMap.has(tabId)) {
const layoutModel = layoutModelMap.get(tabData.oid);
if (layoutModel) {
return layoutModel;
}
}
const layoutTreeStateAtom = withLayoutTreeStateAtomFromTab(tabAtom);
const layoutModel = new LayoutModel(layoutTreeStateAtom, globalStore.get, globalStore.set);
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(layoutModel.onTreeStateAtomUpdated.bind(layoutModel)));
layoutModelMap.set(tabId, layoutModel);
return layoutModel;
return computeIfAbsent(layoutModelMap, tabId, (_) => {
const layoutTreeStateAtom = withLayoutTreeStateAtomFromTab(tabAtom);
const layoutModel = new LayoutModel(layoutTreeStateAtom, globalStore.get, globalStore.set);
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(layoutModel.onTreeStateAtomUpdated.bind(layoutModel)));
return layoutModel;
})
}

export function getLayoutModelForTabById(tabId: string) {
Expand Down Expand Up @@ -73,25 +69,26 @@ export function useDebouncedNodeInnerRect(nodeModel: NodeModel): CSSProperties {
const isResizing = useAtomValue(nodeModel.isResizing);
const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom);
const [innerRect, setInnerRect] = useState<CSSProperties>();
const [innerRectDebounceTimeout, setInnerRectDebounceTimeout] = useState<NodeJS.Timeout>();

const setInnerRectDebounced = useCallback(
(nodeInnerRect: CSSProperties) => {
clearInnerRectDebounce();
setInnerRectDebounceTimeout(
setTimeout(() => {
setInnerRect(nodeInnerRect);
}, animationTimeS * 1000)
);
const timeout = setTimeout(() => {
setInnerRect(nodeInnerRect);
}, animationTimeS * 1000);
computeIfAbsent(timeoutMap, nodeModel.blockId, (_) => timeout)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect usage of computeIfAbsent for timeouts.

The current implementation won't update existing timeouts because computeIfAbsent only sets the value if the key doesn't exist. This could lead to stale timeouts.

Replace with direct map setting:

-            computeIfAbsent(timeoutMap, nodeModel.blockId, (_) => timeout)
+            timeoutMap.set(nodeModel.blockId, timeout);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const timeout = setTimeout(() => {
setInnerRect(nodeInnerRect);
}, animationTimeS * 1000);
computeIfAbsent(timeoutMap, nodeModel.blockId, (_) => timeout)
const timeout = setTimeout(() => {
setInnerRect(nodeInnerRect);
}, animationTimeS * 1000);
timeoutMap.set(nodeModel.blockId, timeout);

},
[animationTimeS]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update dependency array for setInnerRectDebounced.

The callback depends on clearInnerRectDebounce and nodeModel.blockId but they're not included in the dependency array.

Add missing dependencies:

-        [animationTimeS]
+        [animationTimeS, clearInnerRectDebounce, nodeModel.blockId]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const timeout = setTimeout(() => {
setInnerRect(nodeInnerRect);
}, animationTimeS * 1000);
computeIfAbsent(timeoutMap, nodeModel.blockId, (_) => timeout)
},
[animationTimeS]
const timeout = setTimeout(() => {
setInnerRect(nodeInnerRect);
}, animationTimeS * 1000);
computeIfAbsent(timeoutMap, nodeModel.blockId, (_) => timeout)
},
[animationTimeS, clearInnerRectDebounce, nodeModel.blockId]

);
const clearInnerRectDebounce = useCallback(() => {
if (innerRectDebounceTimeout) {
clearTimeout(innerRectDebounceTimeout);
setInnerRectDebounceTimeout(undefined);
const clearInnerRectDebounce = function () {
if (timeoutMap.has(nodeModel.blockId)) {
const innerRectDebounceTimeout = timeoutMap.get(nodeModel.blockId);
if (innerRectDebounceTimeout) {
clearTimeout(innerRectDebounceTimeout);
}
timeoutMap.delete(nodeModel.blockId);
}
}, [innerRectDebounceTimeout]);
};
Comment on lines +88 to +96
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add cleanup on component unmount.

The timeout needs to be cleared when the component unmounts to prevent memory leaks and potential state updates on unmounted components.

Add a cleanup effect:

+    useEffect(() => {
+        return () => clearInnerRectDebounce();
+    }, []);

Committable suggestion skipped: line range outside the PR's diff.


useEffect(() => {
if (prefersReducedMotion || isMagnified || isResizing) {
Expand All @@ -104,3 +101,11 @@ export function useDebouncedNodeInnerRect(nodeModel: NodeModel): CSSProperties {

return innerRect;
}

function computeIfAbsent<V, F>(map: Map<V, F>, key: V, mappingFunction: (a: V) => F): F {
if (!map.has(key)) {
const newValue = mappingFunction(key);
map.set(key, newValue);
}
return map.get(key);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and documentation of computeIfAbsent.

The utility function needs improvements in type safety and documentation:

  1. The return type should handle undefined values from Map.get()
  2. Missing JSDoc documentation

Apply these improvements:

+/**
+ * Returns the value associated with the key if it exists in the map,
+ * otherwise computes a new value using the mapping function, stores it, and returns it.
+ * @param map - The map to check/update
+ * @param key - The key to look up
+ * @param mappingFunction - Function to compute the value if not present
+ * @throws Error if the computed value is undefined
+ */
-function computeIfAbsent<V, F>(map: Map<V, F>, key: V, mappingFunction: (a: V) => F): F {
+function computeIfAbsent<V, F>(map: Map<V, F>, key: V, mappingFunction: (a: V) => F): F {
     if (!map.has(key)) {
         const newValue = mappingFunction(key);
+        if (newValue === undefined) {
+            throw new Error(`Mapping function returned undefined for key: ${key}`);
+        }
         map.set(key, newValue);
     }
-    return map.get(key);
+    return map.get(key)!;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function computeIfAbsent<V, F>(map: Map<V, F>, key: V, mappingFunction: (a: V) => F): F {
if (!map.has(key)) {
const newValue = mappingFunction(key);
map.set(key, newValue);
}
return map.get(key);
}
/**
* Returns the value associated with the key if it exists in the map,
* otherwise computes a new value using the mapping function, stores it, and returns it.
* @param map - The map to check/update
* @param key - The key to look up
* @param mappingFunction - Function to compute the value if not present
* @throws Error if the computed value is undefined
*/
function computeIfAbsent<V, F>(map: Map<V, F>, key: V, mappingFunction: (a: V) => F): F {
if (!map.has(key)) {
const newValue = mappingFunction(key);
if (newValue === undefined) {
throw new Error(`Mapping function returned undefined for key: ${key}`);
}
map.set(key, newValue);
}
return map.get(key)!;
}