2024-08-21 02:01:29 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-10-17 23:34:02 +02:00
|
|
|
import { useOnResize } from "@/app/hook/useDimensions";
|
2024-08-26 20:56:00 +02:00
|
|
|
import { atoms, globalStore, WOS } from "@/app/store/global";
|
2024-08-28 03:38:57 +02:00
|
|
|
import { fireAndForget } from "@/util/util";
|
2024-08-15 22:45:45 +02:00
|
|
|
import { Atom, useAtomValue } from "jotai";
|
2024-12-02 19:56:56 +01:00
|
|
|
import { CSSProperties, useCallback, useEffect, useState } from "react";
|
2024-08-15 03:40:41 +02:00
|
|
|
import { withLayoutTreeStateAtomFromTab } from "./layoutAtom";
|
2024-08-15 04:43:25 +02:00
|
|
|
import { LayoutModel } from "./layoutModel";
|
2024-08-26 20:56:00 +02:00
|
|
|
import { LayoutNode, NodeModel, TileLayoutContents } from "./types";
|
2024-08-15 03:40:41 +02:00
|
|
|
|
|
|
|
const layoutModelMap: Map<string, LayoutModel> = 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);
|
2024-12-06 03:09:54 +01:00
|
|
|
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(layoutModel.onTreeStateAtomUpdated.bind(layoutModel)));
|
2024-08-15 03:40:41 +02:00
|
|
|
layoutModelMap.set(tabId, layoutModel);
|
|
|
|
return layoutModel;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getLayoutModelForTabById(tabId: string) {
|
|
|
|
const tabOref = WOS.makeORef("tab", tabId);
|
|
|
|
const tabAtom = WOS.getWaveObjectAtom<Tab>(tabOref);
|
|
|
|
return getLayoutModelForTab(tabAtom);
|
|
|
|
}
|
|
|
|
|
2024-10-17 23:34:02 +02:00
|
|
|
export function getLayoutModelForStaticTab() {
|
|
|
|
const tabId = globalStore.get(atoms.staticTabId);
|
2024-08-26 20:56:00 +02:00
|
|
|
return getLayoutModelForTabById(tabId);
|
|
|
|
}
|
|
|
|
|
2024-08-15 03:40:41 +02:00
|
|
|
export function deleteLayoutModelForTab(tabId: string) {
|
|
|
|
if (layoutModelMap.has(tabId)) layoutModelMap.delete(tabId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useLayoutModel(tabAtom: Atom<Tab>): LayoutModel {
|
|
|
|
return getLayoutModelForTab(tabAtom);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useTileLayout(tabAtom: Atom<Tab>, tileContent: TileLayoutContents): LayoutModel {
|
2024-08-21 05:14:14 +02:00
|
|
|
// Use tab data to ensure we can reload if the tab is disposed and remade (such as during Hot Module Reloading)
|
|
|
|
useAtomValue(tabAtom);
|
2024-08-15 03:40:41 +02:00
|
|
|
const layoutModel = useLayoutModel(tabAtom);
|
2024-10-17 23:34:02 +02:00
|
|
|
|
|
|
|
useOnResize(layoutModel?.displayContainerRef, layoutModel?.onContainerResize);
|
2024-09-03 23:26:29 +02:00
|
|
|
|
|
|
|
// Once the TileLayout is mounted, re-run the state update to get all the nodes to flow in the layout.
|
2024-12-06 03:09:54 +01:00
|
|
|
useEffect(() => fireAndForget(() => layoutModel.onTreeStateAtomUpdated(true)), []);
|
2024-09-03 23:26:29 +02:00
|
|
|
|
2024-08-15 03:40:41 +02:00
|
|
|
useEffect(() => layoutModel.registerTileLayout(tileContent), [tileContent]);
|
|
|
|
return layoutModel;
|
|
|
|
}
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
export function useNodeModel(layoutModel: LayoutModel, layoutNode: LayoutNode): NodeModel {
|
|
|
|
return layoutModel.getNodeModel(layoutNode);
|
2024-08-15 03:40:41 +02:00
|
|
|
}
|
2024-08-27 22:41:36 +02:00
|
|
|
|
|
|
|
export function useDebouncedNodeInnerRect(nodeModel: NodeModel): CSSProperties {
|
|
|
|
const nodeInnerRect = useAtomValue(nodeModel.innerRect);
|
2024-09-07 01:20:27 +02:00
|
|
|
const animationTimeS = useAtomValue(nodeModel.animationTimeS);
|
|
|
|
const isMagnified = useAtomValue(nodeModel.isMagnified);
|
|
|
|
const isResizing = useAtomValue(nodeModel.isResizing);
|
|
|
|
const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom);
|
2024-08-27 22:41:36 +02:00
|
|
|
const [innerRect, setInnerRect] = useState<CSSProperties>();
|
2024-12-02 19:56:56 +01:00
|
|
|
const [innerRectDebounceTimeout, setInnerRectDebounceTimeout] = useState<NodeJS.Timeout>();
|
2024-08-27 22:41:36 +02:00
|
|
|
|
2024-09-07 01:20:27 +02:00
|
|
|
const setInnerRectDebounced = useCallback(
|
2024-12-02 19:56:56 +01:00
|
|
|
(nodeInnerRect: CSSProperties) => {
|
|
|
|
clearInnerRectDebounce();
|
|
|
|
setInnerRectDebounceTimeout(
|
|
|
|
setTimeout(() => {
|
|
|
|
setInnerRect(nodeInnerRect);
|
|
|
|
}, animationTimeS * 1000)
|
|
|
|
);
|
|
|
|
},
|
2024-09-07 01:20:27 +02:00
|
|
|
[animationTimeS]
|
|
|
|
);
|
2024-12-02 19:56:56 +01:00
|
|
|
const clearInnerRectDebounce = useCallback(() => {
|
|
|
|
if (innerRectDebounceTimeout) {
|
|
|
|
clearTimeout(innerRectDebounceTimeout);
|
|
|
|
setInnerRectDebounceTimeout(undefined);
|
|
|
|
}
|
|
|
|
}, [innerRectDebounceTimeout]);
|
2024-08-28 22:28:49 +02:00
|
|
|
|
2024-12-02 19:56:56 +01:00
|
|
|
useEffect(() => {
|
2024-09-07 01:20:27 +02:00
|
|
|
if (prefersReducedMotion || isMagnified || isResizing) {
|
2024-12-02 19:56:56 +01:00
|
|
|
clearInnerRectDebounce();
|
2024-08-28 22:28:49 +02:00
|
|
|
setInnerRect(nodeInnerRect);
|
2024-09-07 01:20:27 +02:00
|
|
|
} else {
|
|
|
|
setInnerRectDebounced(nodeInnerRect);
|
2024-08-27 22:41:36 +02:00
|
|
|
}
|
2024-09-07 01:20:27 +02:00
|
|
|
}, [nodeInnerRect]);
|
2024-08-27 22:41:36 +02:00
|
|
|
|
|
|
|
return innerRect;
|
|
|
|
}
|