// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { WOS } from "@/app/store/global"; import { Atom, Getter, PrimitiveAtom, WritableAtom, atom, useAtom } from "jotai"; import { useCallback } from "react"; import { layoutTreeStateReducer, newLayoutTreeState } from "./layoutState.js"; import { LayoutNode, LayoutNodeWaveObj, LayoutTreeAction, LayoutTreeState, WritableLayoutNodeAtom, WritableLayoutTreeStateAtom, } from "./model.js"; // map from tabId => layout transforms (sizes and positions of the nodes) let globalLayoutTransformsMap = new Map>(); /** * Creates a new layout tree state wrapped as an atom. * @param rootNode The root node for the tree. * @returns The state wrapped as an atom. * * @template T The type of data associated with the nodes of the tree. */ export function newLayoutTreeStateAtom(rootNode: LayoutNode): PrimitiveAtom> { return atom(newLayoutTreeState(rootNode)) as PrimitiveAtom>; } /** * Derives a WritableLayoutTreeStateAtom from a WritableLayoutNodeAtom, initializing the tree state. * @param layoutNodeAtom The atom containing the root node for the LayoutTreeState. * @returns The derived WritableLayoutTreeStateAtom. */ export function withLayoutTreeState(layoutNodeAtom: WritableLayoutNodeAtom): WritableLayoutTreeStateAtom { const pendingActionAtom = atom(null) as PrimitiveAtom; const generationAtom = atom(0) as PrimitiveAtom; return atom( (get) => { const layoutState = newLayoutTreeState(get(layoutNodeAtom)); layoutState.pendingAction = get(pendingActionAtom); layoutState.generation = get(generationAtom); return layoutState; }, (get, set, value) => { set(pendingActionAtom, value.pendingAction); if (get(generationAtom) !== value.generation) { set(generationAtom, value.generation); set(layoutNodeAtom, value.rootNode); } } ); } /** * Hook to subscribe to the tree state and dispatch actions to its reducer functon. * @param layoutTreeStateAtom The atom holding the layout tree state. * @returns The current state of the tree and the dispatch function. */ export function useLayoutTreeStateReducerAtom( layoutTreeStateAtom: WritableLayoutTreeStateAtom ): readonly [LayoutTreeState, (action: LayoutTreeAction) => void] { const [state, setState] = useAtom(layoutTreeStateAtom); const dispatch = useCallback( (action: LayoutTreeAction) => setState(layoutTreeStateReducer(state, action)), [state, setState] ); return [state, dispatch]; } const tabLayoutAtomCache = new Map>(); function getLayoutNodeWaveObjAtomFromTab( tabAtom: Atom, get: Getter ): WritableAtom, [value: LayoutNodeWaveObj], void> { const tabValue = get(tabAtom); // console.log("getLayoutNodeWaveObjAtomFromTab tabValue", tabValue); if (!tabValue) return; const layoutNodeOref = WOS.makeORef("layout", tabValue.layoutnode); // console.log("getLayoutNodeWaveObjAtomFromTab oref", layoutNodeOref); return WOS.getWaveObjectAtom>(layoutNodeOref); } export function withLayoutStateAtomFromTab(tabAtom: Atom): WritableLayoutTreeStateAtom { const pendingActionAtom = atom(null) as PrimitiveAtom; const generationAtom = atom(0) as PrimitiveAtom; return atom( (get) => { const waveObjAtom = getLayoutNodeWaveObjAtomFromTab(tabAtom, get); if (!waveObjAtom) return null; const waveObj = get(waveObjAtom); const layoutState = newLayoutTreeState(waveObj?.node); layoutState.pendingAction = get(pendingActionAtom); layoutState.generation = get(generationAtom); layoutState.magnifiedNodeId = waveObj?.magnifiednodeid; return layoutState; }, (get, set, value) => { set(pendingActionAtom, value.pendingAction); if (get(generationAtom) !== value.generation) { const waveObjAtom = getLayoutNodeWaveObjAtomFromTab(tabAtom, get); if (!waveObjAtom) return; const newWaveObj = { ...get(waveObjAtom), node: value.rootNode, magnifiednodeid: value.magnifiedNodeId, }; set(generationAtom, value.generation); set(waveObjAtom, newWaveObj); } } ); } export function getLayoutStateAtomForTab( tabId: string, tabAtom: WritableAtom ): WritableLayoutTreeStateAtom { let atom = tabLayoutAtomCache.get(tabId); if (atom) { // console.log("Reusing atom for tab", tabId); return atom; } // console.log("Creating new atom for tab", tabId); atom = withLayoutStateAtomFromTab(tabAtom); tabLayoutAtomCache.set(tabId, atom); return atom; } export function deleteLayoutStateAtomForTab(tabId: string) { const atom = tabLayoutAtomCache.get(tabId); if (atom) { tabLayoutAtomCache.delete(tabId); } } export { globalLayoutTransformsMap };