import { DockManager } from "./DockManager.js"; import { DockModel } from "./DockModel.js"; import { DockNode } from "./DockNode.js"; import { PanelContainer } from "./PanelContainer.js"; import { HorizontalDockContainer } from "./HorizontalDockContainer.js"; import { VerticalDockContainer } from "./VerticalDockContainer.js"; import { DocumentManagerContainer } from "./DocumentManagerContainer.js"; import { FillDockContainer } from "./FillDockContainer.js"; import { Dialog } from "./Dialog.js"; import { Utils } from "./Utils.js"; import { IPanelInfo } from "./interfaces/IPanelInfo.js"; import { INodeInfo } from "./interfaces/INodeInfo.js"; import { IDockContainer } from "./interfaces/IDockContainer.js"; /** * Deserializes the dock layout hierarchy from JSON and creates a dock hierarhcy graph */ export class DockGraphDeserializer { dockManager: DockManager; documentManagerNode: DockNode; constructor(dockManager: DockManager) { this.dockManager = dockManager; } async deserialize(_json: string): Promise { let info = JSON.parse(_json); let model = new DockModel(); model.rootNode = await this._buildGraph(info.graphInfo); model.dialogs = await this._buildDialogs(info.dialogsInfo); model.documentManagerNode = this.documentManagerNode; model.autoHideLayout = info.autoHideLayout; for (const entry of info.autoHidePanels ?? []) { if (!['left', 'right', 'top', 'bottom'].includes(entry.side)) continue; const panel = await PanelContainer.loadFromState(entry.state, this.dockManager); if (panel) model.autoHidePanels.push({ panel, side: entry.side, width: entry.state.width, height: entry.state.height, tabGroupElement: entry.tabGroupElement, restoreLocations: entry.restoreLocations }); } return model; } async _buildGraph(nodeInfo: INodeInfo) { let childrenInfo = nodeInfo.children; let children: DockNode[] = []; for (let childInfo of childrenInfo) { let childNode = await this._buildGraph(childInfo); if (childNode !== null) { children.push(childNode); } }; // Build the container owned by this node let container = await this._createContainer(nodeInfo, children); if (container === null) { return null; } // Build the node for this container and attach it's children let node = new DockNode(container); if (container instanceof DocumentManagerContainer) this.documentManagerNode = node; node.children = children; for (let childNode of node.children.reverse()) { childNode.parent = node; }; node.children.reverse(); // node.container.setActiveChild(node.container); return node; } async _createContainer(nodeInfo: INodeInfo, children: DockNode[]) { let containerType = nodeInfo.containerType; let containerState = nodeInfo.state; let container; let childContainers: IDockContainer[] = []; for (let childNode of children) { childContainers.push(childNode.container); } if (containerType === 'panel') { container = await PanelContainer.loadFromState(containerState, this.dockManager); if (!container?.prepareForDocking) return null; container.prepareForDocking(); Utils.removeNode(container.elementPanel); } else if (containerType === 'horizontal') container = new HorizontalDockContainer(this.dockManager, childContainers); else if (containerType === 'vertical') container = new VerticalDockContainer(this.dockManager, childContainers); else if (containerType === 'fill') { // Check if this is a document manager // TODO: Layout engine compares the string 'fill', so cannot create another subclass type // called document_manager and have to resort to this hack. use RTTI in layout engine let typeDocumentManager = containerState.documentManager; if (typeDocumentManager) container = new DocumentManagerContainer(this.dockManager); else container = new FillDockContainer(this.dockManager); } else throw new Error('Cannot create dock container of unknown type: ' + containerType); // Restore the state of the container container.loadState(containerState); // container.performLayout(childContainers); return container; } async _buildDialogs(dialogsInfo: IPanelInfo[]) { let dialogs: Dialog[] = []; if (!this.dockManager.config?.enableTabGroups || this.dockManager.config.persistTabGroups === false) { dialogsInfo = dialogsInfo.flatMap(info => info.panels ? info.panels.map((state, index) => ({ containerType: info.containerType, state: { ...state, width: info.state.width, height: info.state.height }, position: { x: info.position.x + index * 24, y: info.position.y + index * 24 }, isHidden: info.isHidden, disableResize: info.disableResize })) : [info]); } for (let dialogInfo of dialogsInfo) { let containerType = dialogInfo.containerType; let containerState = dialogInfo.state; let container; if (containerType === 'panel') { const members: PanelContainer[] = []; for (const state of dialogInfo.panels ?? [containerState]) { const member = await PanelContainer.loadFromState(state, this.dockManager); if (member) members.push(member); } container = (dialogInfo.activePanel && members.find(panel => panel.elementContent.id === dialogInfo.activePanel)) || members[0]; if (container?.prepareForDocking) { Utils.removeNode(container.elementPanel); container.isDialog = true; let dialog = new Dialog(container, this.dockManager, undefined, dialogInfo.disableResize, members); if (dialogInfo.panels) await dialog.resize(containerState.width, containerState.height); if (dialogInfo.position.x > document.body.clientWidth || dialogInfo.position.y > document.body.clientHeight - 70) { dialogInfo.position.x = 20; dialogInfo.position.y = 70; } dialog.setPosition(dialogInfo.position.x, dialogInfo.position.y); dialog.isHidden = dialogInfo.isHidden; if (dialog.isHidden) dialog.hide(); dialogs.push(dialog); } } } return dialogs; } }