UNPKG

flexlayout-react

Version:

A multi-tab docking layout manager

921 lines 43.4 kB
import * as React from "react"; import { createPortal } from "react-dom"; import { createRoot } from "react-dom/client"; import { DockLocation } from "../DockLocation"; import { I18nLabel } from "../I18nLabel"; import { Orientation } from "../Orientation"; import { Rect } from "../Rect"; import { CLASSES } from "../Types"; import { Actions } from "../model/Actions"; import { BorderNode } from "../model/BorderNode"; import { Model } from "../model/Model"; import { TabNode } from "../model/TabNode"; import { TabSetNode } from "../model/TabSetNode"; import { BorderTab } from "./BorderTab"; import { BorderTabSet } from "./BorderTabSet"; import { DragContainer } from "./DragContainer"; import { ErrorBoundary } from "./ErrorBoundary"; import { PopoutWindow } from "./PopoutWindow"; import { AsterickIcon, CloseIcon, EdgeIcon, MaximizeIcon, OverflowIcon, PopoutIcon, RestoreIcon } from "./Icons"; import { Overlay } from "./Overlay"; import { Row } from "./Row"; import { Tab } from "./Tab"; import { copyInlineStyles, enablePointerOnIFrames, isDesktop, isSafari } from "./Utils"; import { TabButtonStamp } from "./TabButtonStamp"; import { SizeTracker } from "./SizeTracker"; /** * A React component that hosts a multi-tabbed layout */ export class Layout extends React.Component { /** @internal */ constructor(props) { super(props); this.selfRef = React.createRef(); this.revision = 0; } /** re-render the layout */ redraw() { this.selfRef.current.redraw("parent " + this.revision); } /** * Adds a new tab to the given tabset * @param tabsetId the id of the tabset where the new tab will be added * @param json the json for the new tab node * @returns the added tab node or undefined */ addTabToTabSet(tabsetId, json) { return this.selfRef.current.addTabToTabSet(tabsetId, json); } /** * Adds a new tab by dragging an item to the drop location, must be called from within an HTML * drag start handler. You can use the setDragComponent() method to set the drag image before calling this * method. * @param event the drag start event * @param json the json for the new tab node * @param onDrop a callback to call when the drag is complete */ addTabWithDragAndDrop(event, json, onDrop) { this.selfRef.current.addTabWithDragAndDrop(event, json, onDrop); } /** * Move a tab/tabset using drag and drop, must be called from within an HTML * drag start handler * @param event the drag start event * @param node the tab or tabset to drag */ moveTabWithDragAndDrop(event, node) { this.selfRef.current.moveTabWithDragAndDrop(event, node); } /** * Adds a new tab to the active tabset (if there is one) * @param json the json for the new tab node * @returns the added tab node or undefined */ addTabToActiveTabSet(json) { return this.selfRef.current.addTabToActiveTabSet(json); } /** * Sets the drag image from a react component for a drag event * @param event the drag event * @param component the react component to be used for the drag image * @param x the x position of the drag cursor on the image * @param y the x position of the drag cursor on the image */ setDragComponent(event, component, x, y) { this.selfRef.current.setDragComponent(event, component, x, y); } /** Get the root div element of the layout */ getRootDiv() { return this.selfRef.current.getRootDiv(); } /** @internal */ render() { return (React.createElement(LayoutInternal, Object.assign({ ref: this.selfRef }, this.props, { renderRevision: this.revision++ }))); } } /** @internal */ export class LayoutInternal extends React.Component { // private renderCount: any; constructor(props) { super(props); this.moveableElementMap = new Map(); this.dragEnterCount = 0; this.dragging = false; this.updateLayoutMetrics = () => { if (this.findBorderBarSizeRef.current) { const borderBarSize = this.findBorderBarSizeRef.current.getBoundingClientRect().height; if (borderBarSize !== this.state.calculatedBorderBarSize) { this.setState({ calculatedBorderBarSize: borderBarSize }); } } }; this.onModelChange = (action) => { this.redrawInternal("model change"); if (this.props.onModelChange) { this.props.onModelChange(this.props.model, action); } }; this.updateRect = () => { const rect = this.getDomRect(); if (!rect.equals(this.state.rect) && rect.width !== 0 && rect.height !== 0) { // console.log("updateRect", rect.floor()); this.setState({ rect }); if (this.windowId !== Model.MAIN_WINDOW_ID) { this.redrawInternal("rect updated"); } } }; this.getClassName = (defaultClassName) => { if (this.props.classNameMapper === undefined) { return defaultClassName; } else { return this.props.classNameMapper(defaultClassName); } }; this.onCloseWindow = (windowLayout) => { this.doAction(Actions.closeWindow(windowLayout.windowId)); }; this.onSetWindow = (windowLayout, window) => { }; this.showControlInPortal = (control, element) => { const portal = createPortal(control, element); this.setState({ portal }); }; this.hideControlInPortal = () => { this.setState({ portal: undefined }); }; this.getIcons = () => { return this.icons; }; this.setDragNode = (event, node) => { LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.Internal, node, undefined, undefined); // Note: can only set (very) limited types on android! so cannot set json // Note: must set text/plain for android to allow drag, // so just set a simple message indicating its a flexlayout drag (this is not used anywhere else) event.dataTransfer.setData('text/plain', "--flexlayout--"); event.dataTransfer.effectAllowed = "copyMove"; event.dataTransfer.dropEffect = "move"; this.dragEnterCount = 0; if (node instanceof TabSetNode) { let rendered = false; let content = this.i18nName(I18nLabel.Move_Tabset); if (node.getChildren().length > 0) { content = this.i18nName(I18nLabel.Move_Tabs).replace("?", String(node.getChildren().length)); } if (this.props.onRenderDragRect) { const dragComponent = this.props.onRenderDragRect(content, node, undefined); if (dragComponent) { this.setDragComponent(event, dragComponent, 10, 10); rendered = true; } } if (!rendered) { this.setDragComponent(event, content, 10, 10); } } else { const element = event.target; const rect = element.getBoundingClientRect(); const offsetX = event.clientX - rect.left; const offsetY = event.clientY - rect.top; const parentNode = node === null || node === void 0 ? void 0 : node.getParent(); const isInVerticalBorder = parentNode instanceof BorderNode && parentNode.getOrientation() === Orientation.HORZ; const x = isInVerticalBorder ? 10 : offsetX; const y = isInVerticalBorder ? 10 : offsetY; let rendered = false; if (this.props.onRenderDragRect) { const content = React.createElement(TabButtonStamp, { key: node.getId(), layout: this, node: node }); const dragComponent = this.props.onRenderDragRect(content, node, undefined); if (dragComponent) { this.setDragComponent(event, dragComponent, x, y); rendered = true; } } if (!rendered) { if (isSafari()) { // safari doesnt render the offscreen tabstamps this.setDragComponent(event, React.createElement(TabButtonStamp, { node: node, layout: this }), x, y); } else { event.dataTransfer.setDragImage(node.getTabStamp(), x, y); } } } }; this.onDragEnterRaw = (event) => { this.dragEnterCount++; if (this.dragEnterCount === 1) { this.onDragEnter(event); } }; this.onDragLeaveRaw = (event) => { this.dragEnterCount--; if (this.dragEnterCount === 0) { this.onDragLeave(event); } }; this.onDragEnter = (event) => { // console.log("onDragEnter", this.windowId, this.dragEnterCount); var _a; if (!LayoutInternal.dragState && this.props.onExternalDrag) { // not internal dragging const externalDrag = this.props.onExternalDrag(event); if (externalDrag) { const tempNode = TabNode.fromJson(externalDrag.json, this.props.model, false); LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.External, tempNode, externalDrag.json, externalDrag.onDrop); } } if (LayoutInternal.dragState) { if (this.windowId !== Model.MAIN_WINDOW_ID && LayoutInternal.dragState.mainLayout === this.mainLayout) { LayoutInternal.dragState.mainLayout.setDraggingOverWindow(true); } if (LayoutInternal.dragState.mainLayout !== this.mainLayout) { return; // drag not by this layout or its popouts } event.preventDefault(); this.dropInfo = undefined; const rootdiv = this.selfRef.current; this.outlineDiv = this.currentDocument.createElement("div"); this.outlineDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__OUTLINE_RECT); this.outlineDiv.style.visibility = "hidden"; const speed = this.props.model.getAttribute("tabDragSpeed"); this.outlineDiv.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`; rootdiv.appendChild(this.outlineDiv); this.dragging = true; this.showOverlay(true); // add edge indicators if (!this.isDraggingOverWindow && this.props.model.getMaximizedTabset(this.windowId) === undefined) { this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); } const clientRect = (_a = this.selfRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect(); const r = new Rect(event.clientX - (clientRect.left), event.clientY - (clientRect.top), 1, 1); r.positionElement(this.outlineDiv); } }; this.onDragOver = (event) => { var _a, _b, _c; if (this.dragging && !this.isDraggingOverWindow) { // console.log("onDragOver"); event.preventDefault(); const clientRect = (_a = this.selfRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect(); const pos = { x: event.clientX - ((_b = clientRect === null || clientRect === void 0 ? void 0 : clientRect.left) !== null && _b !== void 0 ? _b : 0), y: event.clientY - ((_c = clientRect === null || clientRect === void 0 ? void 0 : clientRect.top) !== null && _c !== void 0 ? _c : 0), }; this.checkForBorderToShow(pos.x, pos.y); let dropInfo = this.props.model.findDropTargetNode(this.windowId, LayoutInternal.dragState.dragNode, pos.x, pos.y); if (dropInfo) { this.dropInfo = dropInfo; if (this.outlineDiv) { this.outlineDiv.className = this.getClassName(dropInfo.className); dropInfo.rect.positionElement(this.outlineDiv); this.outlineDiv.style.visibility = "visible"; } } } }; this.onDragLeave = (event) => { // console.log("onDragLeave", this.windowId, this.dragging); if (this.dragging) { if (this.windowId !== Model.MAIN_WINDOW_ID) { LayoutInternal.dragState.mainLayout.setDraggingOverWindow(false); } this.clearDragLocal(); } }; this.onDrop = (event) => { // console.log("ondrop", this.windowId, this.dragging, Layout.dragState); if (this.dragging) { event.preventDefault(); const dragState = LayoutInternal.dragState; if (this.dropInfo) { if (dragState.dragJson !== undefined) { const newNode = this.doAction(Actions.addNode(dragState.dragJson, this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index)); if (dragState.fnNewNodeDropped !== undefined) { dragState.fnNewNodeDropped(newNode, event); } } else if (dragState.dragNode !== undefined) { this.doAction(Actions.moveNode(dragState.dragNode.getId(), this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index)); } } this.mainLayout.clearDragMain(); } this.dragEnterCount = 0; // must set to zero here ref sublayouts }; this.orderedIds = []; this.selfRef = React.createRef(); this.moveablesRef = React.createRef(); this.mainRef = React.createRef(); this.findBorderBarSizeRef = React.createRef(); this.supportsPopout = props.supportsPopout !== undefined ? props.supportsPopout : defaultSupportsPopout; this.popoutURL = props.popoutURL ? props.popoutURL : "popout.html"; this.icons = Object.assign(Object.assign({}, defaultIcons), props.icons); this.windowId = props.windowId ? props.windowId : Model.MAIN_WINDOW_ID; this.mainLayout = this.props.mainLayout ? this.props.mainLayout : this; this.isDraggingOverWindow = false; this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId); this.layoutWindow.layout = this; this.popoutWindowName = this.props.popoutWindowName || "Popout Window"; // this.renderCount = 0; this.state = { rect: Rect.empty(), editingTab: undefined, showEdges: false, showOverlay: false, calculatedBorderBarSize: 29, layoutRevision: 0, forceRevision: 0, showHiddenBorder: DockLocation.CENTER }; this.isMainWindow = this.windowId === Model.MAIN_WINDOW_ID; } componentDidMount() { this.updateRect(); this.currentDocument = this.selfRef.current.ownerDocument; this.currentWindow = this.currentDocument.defaultView; this.layoutWindow.window = this.currentWindow; this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); this.resizeObserver = new ResizeObserver(entries => { requestAnimationFrame(() => { this.updateRect(); }); }); if (this.selfRef.current) { this.resizeObserver.observe(this.selfRef.current); } if (this.isMainWindow) { this.props.model.addChangeListener(this.onModelChange); this.updateLayoutMetrics(); } else { // since resizeObserver doesn't always work as expected when observing element in another document this.currentWindow.addEventListener("resize", () => { this.updateRect(); }); const sourceElement = this.props.mainLayout.getRootDiv(); const targetElement = this.selfRef.current; copyInlineStyles(sourceElement, targetElement); this.styleObserver = new MutationObserver(() => { const changed = copyInlineStyles(sourceElement, targetElement); if (changed) { this.redraw("mutation observer"); } }); // Observe changes to the source element's style attribute this.styleObserver.observe(sourceElement, { attributeFilter: ['style'] }); } // allow tabs to overlay when hidden document.addEventListener('visibilitychange', () => { for (const [_, layoutWindow] of this.props.model.getwindowsMap()) { const layout = layoutWindow.layout; if (layout) { this.redraw("visibility change"); } } }); } componentDidUpdate() { this.currentDocument = this.selfRef.current.ownerDocument; this.currentWindow = this.currentDocument.defaultView; if (this.isMainWindow) { if (this.props.model !== this.previousModel) { if (this.previousModel !== undefined) { this.previousModel.removeChangeListener(this.onModelChange); // stop listening to old model } this.props.model.getwindowsMap().get(this.windowId).layout = this; this.props.model.addChangeListener(this.onModelChange); this.layoutWindow = this.props.model.getwindowsMap().get(this.windowId); this.layoutWindow.layout = this; this.layoutWindow.toScreenRectFunction = (r) => this.getScreenRect(r); this.previousModel = this.props.model; this.tidyMoveablesMap(); } this.updateLayoutMetrics(); } } componentWillUnmount() { var _a, _b; if (this.selfRef.current) { (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.unobserve(this.selfRef.current); } (_b = this.styleObserver) === null || _b === void 0 ? void 0 : _b.disconnect(); } render() { // console.log("render", this.windowId, this.state.revision, this.renderCount++); // first render will be used to find the size (via selfRef) if (!this.selfRef.current) { return (React.createElement("div", { ref: this.selfRef, className: this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT) }, React.createElement("div", { ref: this.moveablesRef, key: "__moveables__", className: this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MOVEABLES) }), this.renderMetricsElements())); } const model = this.props.model; model.getRoot(this.windowId).calcMinMaxSize(); model.getRoot(this.windowId).setPaths(""); model.getBorderSet().setPaths(); const inner = this.renderLayout(); const outer = this.renderBorders(inner); const tabs = this.renderTabs(); const reorderedTabs = this.reorderComponents(tabs, this.orderedIds); let floatingWindows = null; let tabMoveables = null; let tabStamps = null; let metricElements = null; if (this.isMainWindow) { floatingWindows = this.renderWindows(); metricElements = this.renderMetricsElements(); tabMoveables = this.renderTabMoveables(); tabStamps = React.createElement("div", { key: "__tabStamps__", className: this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_TAB_STAMPS) }, this.renderTabStamps()); } return (React.createElement("div", { ref: this.selfRef, className: this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT), onDragEnter: this.onDragEnterRaw, onDragLeave: this.onDragLeaveRaw, onDragOver: this.onDragOver, onDrop: this.onDrop }, React.createElement("div", { ref: this.moveablesRef, key: "__moveables__", className: this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MOVEABLES) }), metricElements, React.createElement(Overlay, { key: "__overlay__", layout: this, show: this.state.showOverlay }), outer, reorderedTabs, tabMoveables, tabStamps, this.state.portal, floatingWindows)); } renderBorders(inner) { const classMain = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_MAIN); const borders = this.props.model.getBorderSet().getBorderMap(); if (this.isMainWindow && borders.size > 0) { inner = (React.createElement("div", { className: classMain, ref: this.mainRef }, inner)); const borderSetComponents = new Map(); const borderSetContentComponents = new Map(); for (const [_, location] of DockLocation.values) { const border = borders.get(location); const showBorder = border && border.isShowing() && (!border.isAutoHide() || (border.isAutoHide() && (border.getChildren().length > 0 || this.state.showHiddenBorder === location))); if (showBorder) { borderSetComponents.set(location, React.createElement(BorderTabSet, { layout: this, border: border, size: this.state.calculatedBorderBarSize })); borderSetContentComponents.set(location, React.createElement(BorderTab, { layout: this, border: border, show: border.getSelected() !== -1 })); } } const classBorderOuter = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER); const classBorderInner = this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT_BORDER_CONTAINER_INNER); if (this.props.model.getBorderSet().getLayoutHorizontal()) { const innerWithBorderTabs = (React.createElement("div", { className: classBorderInner, style: { flexDirection: "column" } }, borderSetContentComponents.get(DockLocation.TOP), React.createElement("div", { className: classBorderInner, style: { flexDirection: "row" } }, borderSetContentComponents.get(DockLocation.LEFT), inner, borderSetContentComponents.get(DockLocation.RIGHT)), borderSetContentComponents.get(DockLocation.BOTTOM))); return (React.createElement("div", { className: classBorderOuter, style: { flexDirection: "column" } }, borderSetComponents.get(DockLocation.TOP), React.createElement("div", { className: classBorderInner, style: { flexDirection: "row" } }, borderSetComponents.get(DockLocation.LEFT), innerWithBorderTabs, borderSetComponents.get(DockLocation.RIGHT)), borderSetComponents.get(DockLocation.BOTTOM))); } else { const innerWithBorderTabs = (React.createElement("div", { className: classBorderInner, style: { flexDirection: "row" } }, borderSetContentComponents.get(DockLocation.LEFT), React.createElement("div", { className: classBorderInner, style: { flexDirection: "column" } }, borderSetContentComponents.get(DockLocation.TOP), inner, borderSetContentComponents.get(DockLocation.BOTTOM)), borderSetContentComponents.get(DockLocation.RIGHT))); return (React.createElement("div", { className: classBorderOuter, style: { flexDirection: "row" } }, borderSetComponents.get(DockLocation.LEFT), React.createElement("div", { className: classBorderInner, style: { flexDirection: "column" } }, borderSetComponents.get(DockLocation.TOP), innerWithBorderTabs, borderSetComponents.get(DockLocation.BOTTOM)), borderSetComponents.get(DockLocation.RIGHT))); } } else { // no borders return (React.createElement("div", { className: classMain, ref: this.mainRef, style: { position: "absolute", top: 0, left: 0, bottom: 0, right: 0, display: "flex" } }, inner)); } } renderLayout() { return (React.createElement(React.Fragment, null, React.createElement(Row, { key: "__row__", layout: this, node: this.props.model.getRoot(this.windowId) }), this.renderEdgeIndicators())); } renderEdgeIndicators() { const edges = []; const arrowIcon = this.icons.edgeArrow; if (this.state.showEdges) { const r = this.props.model.getRoot(this.windowId).getRect(); const length = edgeRectLength; const width = edgeRectWidth; const offset = edgeRectLength / 2; const className = this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT); const radius = 50; edges.push(React.createElement("div", { key: "North", style: { top: 0, left: r.width / 2 - offset, width: length, height: width, borderBottomLeftRadius: radius, borderBottomRightRadius: radius }, className: className + " " + this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT_TOP) }, React.createElement("div", { style: { transform: "rotate(180deg)" } }, arrowIcon))); edges.push(React.createElement("div", { key: "West", style: { top: r.height / 2 - offset, left: 0, width: width, height: length, borderTopRightRadius: radius, borderBottomRightRadius: radius }, className: className + " " + this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT_LEFT) }, React.createElement("div", { style: { transform: "rotate(90deg)" } }, arrowIcon))); edges.push(React.createElement("div", { key: "South", style: { top: r.height - width, left: r.width / 2 - offset, width: length, height: width, borderTopLeftRadius: radius, borderTopRightRadius: radius }, className: className + " " + this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT_BOTTOM) }, React.createElement("div", null, arrowIcon))); edges.push(React.createElement("div", { key: "East", style: { top: r.height / 2 - offset, left: r.width - width, width: width, height: length, borderTopLeftRadius: radius, borderBottomLeftRadius: radius }, className: className + " " + this.getClassName(CLASSES.FLEXLAYOUT__EDGE_RECT_RIGHT) }, React.createElement("div", { style: { transform: "rotate(-90deg)" } }, arrowIcon))); } return edges; } renderWindows() { const floatingWindows = []; if (this.supportsPopout) { const windows = this.props.model.getwindowsMap(); let i = 1; for (const [windowId, layoutWindow] of windows) { if (windowId !== Model.MAIN_WINDOW_ID) { floatingWindows.push(React.createElement(PopoutWindow, { key: windowId, layout: this, title: this.popoutWindowName + " " + i, layoutWindow: layoutWindow, url: this.popoutURL + "?id=" + windowId, onSetWindow: this.onSetWindow, onCloseWindow: this.onCloseWindow }, React.createElement("div", { className: this.props.popoutClassName }, React.createElement(LayoutInternal, Object.assign({}, this.props, { windowId: windowId, mainLayout: this }))))); i++; } } } return floatingWindows; } renderTabMoveables() { const tabMoveables = []; this.props.model.visitNodes((node) => { if (node instanceof TabNode) { const child = node; const element = this.getMoveableElement(child.getId()); child.setMoveableElement(element); const selected = child.isSelected(); const rect = child.getParent().getContentRect(); // only render first time if size >0 const renderTab = child.isRendered() || ((selected || !child.isEnableRenderOnDemand()) && (rect.width > 0 && rect.height > 0)); if (renderTab) { // console.log("rendertab", child.getName(), this.props.renderRevision); const key = child.getId() + (child.isEnableWindowReMount() ? child.getWindowId() : ""); tabMoveables.push(createPortal(React.createElement(SizeTracker, { rect: rect, selected: child.isSelected(), forceRevision: this.state.forceRevision, tabsRevision: this.props.renderRevision, key: key }, React.createElement(ErrorBoundary, { message: this.i18nName(I18nLabel.Error_rendering_component) }, this.props.factory(child))), element, key)); child.setRendered(renderTab); } } }); return tabMoveables; } renderTabStamps() { const tabStamps = []; this.props.model.visitNodes((node) => { if (node instanceof TabNode) { const child = node; // what the tab should look like when dragged (since images need to have been loaded before drag image can be taken) tabStamps.push(React.createElement(DragContainer, { key: child.getId(), layout: this, node: child })); } }); return tabStamps; } renderTabs() { const tabs = new Map(); this.props.model.visitWindowNodes(this.windowId, (node) => { if (node instanceof TabNode) { const child = node; const selected = child.isSelected(); const path = child.getPath(); const renderTab = child.isRendered() || selected || !child.isEnableRenderOnDemand(); if (renderTab) { // const rect = (child.getParent() as BorderNode | TabSetNode).getContentRect(); // const key = child.getId(); tabs.set(child.getId(), ( // <SizeTracker rect={rect} forceRevision={this.state.forceRevision} key={key}> React.createElement(Tab, { key: child.getId(), layout: this, path: path, node: child, selected: selected }) // </SizeTracker> )); } } }); return tabs; } renderMetricsElements() { return (React.createElement("div", { key: "findBorderBarSize", ref: this.findBorderBarSizeRef, className: this.getClassName(CLASSES.FLEXLAYOUT__BORDER_SIZER) }, "FindBorderBarSize")); } checkForBorderToShow(x, y) { const r = this.getBoundingClientRect(this.mainRef.current); const c = r.getCenter(); const margin = edgeRectWidth; const offset = edgeRectLength / 2; let overEdge = false; if (this.props.model.isEnableEdgeDock() && this.state.showHiddenBorder === DockLocation.CENTER) { if ((y > c.y - offset && y < c.y + offset) || (x > c.x - offset && x < c.x + offset)) { overEdge = true; } } let location = DockLocation.CENTER; if (!overEdge) { if (x <= r.x + margin) { location = DockLocation.LEFT; } else if (x >= r.getRight() - margin) { location = DockLocation.RIGHT; } else if (y <= r.y + margin) { location = DockLocation.TOP; } else if (y >= r.getBottom() - margin) { location = DockLocation.BOTTOM; } } if (location !== this.state.showHiddenBorder) { this.setState({ showHiddenBorder: location }); } } tidyMoveablesMap() { // console.log("tidyMoveablesMap"); const tabs = new Map(); this.props.model.visitNodes((node, _) => { if (node instanceof TabNode) { tabs.set(node.getId(), node); } }); for (const [nodeId, element] of this.moveableElementMap) { if (!tabs.has(nodeId)) { // console.log("delete", nodeId); element.remove(); // remove from dom this.moveableElementMap.delete(nodeId); // remove map entry } } } reorderComponents(components, ids) { const nextIds = []; const nextIdsSet = new Set(); let reordered = []; // Keep any previous tabs in the same DOM order as before, removing any that have been deleted for (const id of ids) { if (components.get(id)) { nextIds.push(id); nextIdsSet.add(id); } } ids.splice(0, ids.length, ...nextIds); // Add tabs that have been added to the DOM for (const [id, _] of components) { if (!nextIdsSet.has(id)) { ids.push(id); } } reordered = ids.map((id) => { return components.get(id); }); return reordered; } redraw(type) { // console.log("redraw", this.windowId, type); this.mainLayout.setState((state, props) => { return { forceRevision: state.forceRevision + 1 }; }); } redrawInternal(type) { // console.log("redrawInternal", this.windowId, type); this.mainLayout.setState((state, props) => { return { layoutRevision: state.layoutRevision + 1 }; }); } doAction(action) { if (this.props.onAction !== undefined) { const outcome = this.props.onAction(action); if (outcome !== undefined) { return this.props.model.doAction(outcome); } return undefined; } else { return this.props.model.doAction(action); } } getBoundingClientRect(div) { const layoutRect = this.getDomRect(); if (layoutRect) { return Rect.getBoundingClientRect(div).relativeTo(layoutRect); } return Rect.empty(); } getMoveableContainer() { return this.moveablesRef.current; } getMoveableElement(id) { let moveableElement = this.moveableElementMap.get(id); if (moveableElement === undefined) { moveableElement = document.createElement("div"); this.moveablesRef.current.appendChild(moveableElement); moveableElement.className = CLASSES.FLEXLAYOUT__TAB_MOVEABLE; this.moveableElementMap.set(id, moveableElement); } return moveableElement; } getMainLayout() { return this.mainLayout; } getCurrentDocument() { return this.currentDocument; } getDomRect() { if (this.selfRef.current) { return Rect.fromDomRect(this.selfRef.current.getBoundingClientRect()); } else { return Rect.empty(); } } getWindowId() { return this.windowId; } getRootDiv() { return this.selfRef.current; } getMainElement() { return this.mainRef.current; } getFactory() { return this.props.factory; } isSupportsPopout() { return this.supportsPopout; } isRealtimeResize() { var _a; return (_a = this.props.realtimeResize) !== null && _a !== void 0 ? _a : false; } getPopoutURL() { return this.popoutURL; } setEditingTab(tabNode) { this.setState({ editingTab: tabNode }); } getEditingTab() { return this.state.editingTab; } getModel() { return this.props.model; } getScreenRect(inRect) { const rect = inRect.clone(); const layoutRect = this.getDomRect(); // Note: outerHeight can be less than innerHeight when window is zoomed, so cannot use // const navHeight = Math.min(65, this.currentWindow!.outerHeight - this.currentWindow!.innerHeight); // const navWidth = Math.min(65, this.currentWindow!.outerWidth - this.currentWindow!.innerWidth); const navHeight = 60; const navWidth = 2; // console.log(rect.y, this.currentWindow!.screenX,layoutRect.y); rect.x = this.currentWindow.screenX + this.currentWindow.scrollX + navWidth / 2 + layoutRect.x + rect.x; rect.y = this.currentWindow.screenY + this.currentWindow.scrollY + (navHeight - navWidth / 2) + layoutRect.y + rect.y; rect.height += navHeight; rect.width += navWidth; return rect; } addTabToTabSet(tabsetId, json) { const tabsetNode = this.props.model.getNodeById(tabsetId); if (tabsetNode !== undefined) { const node = this.doAction(Actions.addNode(json, tabsetId, DockLocation.CENTER, -1)); return node; } return undefined; } addTabToActiveTabSet(json) { const tabsetNode = this.props.model.getActiveTabset(this.windowId); if (tabsetNode !== undefined) { const node = this.doAction(Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1)); return node; } return undefined; } maximize(tabsetNode) { this.doAction(Actions.maximizeToggle(tabsetNode.getId(), this.getWindowId())); } customizeTab(tabNode, renderValues) { if (this.props.onRenderTab) { this.props.onRenderTab(tabNode, renderValues); } } customizeTabSet(tabSetNode, renderValues) { if (this.props.onRenderTabSet) { this.props.onRenderTabSet(tabSetNode, renderValues); } } i18nName(id, param) { let message; if (this.props.i18nMapper) { message = this.props.i18nMapper(id, param); } if (message === undefined) { message = id + (param === undefined ? "" : param); } return message; } getShowOverflowMenu() { return this.props.onShowOverflowMenu; } getTabSetPlaceHolderCallback() { return this.props.onTabSetPlaceHolder; } showContextMenu(node, event) { if (this.props.onContextMenu) { this.props.onContextMenu(node, event); } } auxMouseClick(node, event) { if (this.props.onAuxMouseClick) { this.props.onAuxMouseClick(node, event); } } showOverlay(show) { this.setState({ showOverlay: show }); enablePointerOnIFrames(!show, this.currentDocument); } // *************************** Start Drag Drop ************************************* addTabWithDragAndDrop(event, json, onDrop) { const tempNode = TabNode.fromJson(json, this.props.model, false); LayoutInternal.dragState = new DragState(this.mainLayout, DragSource.Add, tempNode, json, onDrop); } moveTabWithDragAndDrop(event, node) { this.setDragNode(event, node); } setDragComponent(event, component, x, y) { let dragElement = (React.createElement("div", { style: { position: "unset" }, className: this.getClassName(CLASSES.FLEXLAYOUT__LAYOUT) + " " + this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT) }, component)); const tempDiv = this.currentDocument.createElement('div'); tempDiv.setAttribute("data-layout-path", "/drag-rectangle"); tempDiv.style.position = "absolute"; tempDiv.style.left = "-10000px"; tempDiv.style.top = "-10000px"; this.currentDocument.body.appendChild(tempDiv); createRoot(tempDiv).render(dragElement); event.dataTransfer.setDragImage(tempDiv, x, y); setTimeout(() => { this.currentDocument.body.removeChild(tempDiv); }, 0); } setDraggingOverWindow(overWindow) { // console.log("setDraggingOverWindow", overWindow); if (this.isDraggingOverWindow !== overWindow) { if (this.outlineDiv) { this.outlineDiv.style.visibility = overWindow ? "hidden" : "visible"; } if (overWindow) { this.setState({ showEdges: false }); } else { // add edge indicators if (this.props.model.getMaximizedTabset(this.windowId) === undefined) { this.setState({ showEdges: this.props.model.isEnableEdgeDock() }); } } this.isDraggingOverWindow = overWindow; } } clearDragMain() { // console.log("clear drag main"); LayoutInternal.dragState = undefined; if (this.windowId === Model.MAIN_WINDOW_ID) { this.isDraggingOverWindow = false; } for (const [, layoutWindow] of this.props.model.getwindowsMap()) { // console.log(layoutWindow); layoutWindow.layout.clearDragLocal(); } } clearDragLocal() { // console.log("clear drag local", this.windowId); this.setState({ showEdges: false }); this.showOverlay(false); this.dragEnterCount = 0; this.dragging = false; if (this.outlineDiv) { this.selfRef.current.removeChild(this.outlineDiv); this.outlineDiv = undefined; } } } LayoutInternal.dragState = undefined; export const FlexLayoutVersion = "0.8.9"; const defaultIcons = { close: React.createElement(CloseIcon, null), closeTabset: React.createElement(CloseIcon, null), popout: React.createElement(PopoutIcon, null), maximize: React.createElement(MaximizeIcon, null), restore: React.createElement(RestoreIcon, null), more: React.createElement(OverflowIcon, null), edgeArrow: React.createElement(EdgeIcon, null), activeTabset: React.createElement(AsterickIcon, null) }; var DragSource; (function (DragSource) { DragSource["Internal"] = "internal"; DragSource["External"] = "external"; DragSource["Add"] = "add"; })(DragSource || (DragSource = {})); /** @internal */ const defaultSupportsPopout = isDesktop(); /** @internal */ const edgeRectLength = 100; /** @internal */ const edgeRectWidth = 10; // global layout drag state class DragState { constructor(mainLayout, dragSource, dragNode, dragJson, fnNewNodeDropped) { this.mainLayout = mainLayout; this.dragSource = dragSource; this.dragNode = dragNode; this.dragJson = dragJson; this.fnNewNodeDropped = fnNewNodeDropped; } } //# sourceMappingURL=Layout.js.map