diff --git a/src/Dialog.ts b/src/Dialog.ts index d200b68..8390a8e 100644 --- a/src/Dialog.ts +++ b/src/Dialog.ts @@ -1,186 +1,99 @@ import { DockManager } from "./DockManager.js"; import { DockNode } from "./DockNode.js"; import { DraggableContainer } from "./DraggableContainer.js"; -import { EventHandler } from "./EventHandler.js"; +import { FloatingPanel } from "./FloatingPanel.js"; import { PanelContainer } from "./PanelContainer.js"; import { Point } from "./Point.js"; import { ResizableContainer } from "./ResizableContainer.js"; import { Utils } from "./Utils.js"; import { ResizeDirection } from "./enums/ResizeDirection.js"; import { Localizer } from "./i18n/Localizer.js"; -import { IContextMenuProvider } from "./interfaces/IContextMenuProvider.js"; -export class Dialog implements IContextMenuProvider { - elementDialog: HTMLDivElement & { floatingDialog: Dialog }; +export class Dialog extends FloatingPanel { draggable: DraggableContainer; - panel: PanelContainer; - dockManager: DockManager; - eventListener: DockManager; - position: Point; resizable: ResizableContainer; - disableResize: boolean; - mouseDownHandler: any; - onKeyPressBound: any; + eventListener: DockManager; noDocking: boolean; - isHidden: boolean; - keyPressHandler: EventHandler; - focusHandler: EventHandler; grayoutParent: PanelContainer; - constructor(panel: PanelContainer, dockManager: DockManager, grayoutParent?: PanelContainer, disableResize?: boolean) { - this.panel = panel; - this.dockManager = dockManager; - this.eventListener = dockManager; + constructor( + panel: PanelContainer, + dockManager: DockManager, + grayoutParent?: PanelContainer, + disableResize?: boolean + ) { + super(panel, dockManager); this.grayoutParent = grayoutParent; - this.disableResize = disableResize; - this._initialize(); - this.dockManager.context.model.dialogs.push(this); - this.position = dockManager.defaultDialogPosition; - this.dockManager.notifyOnCreateDialog(this); - panel.isDialog = true; - } - - saveState(x: number, y: number) { - this.position = new Point(x, y); - this.dockManager.notifyOnChangeDialogPosition(this, x, y); - } - - static fromElement(id: string, dockManager: DockManager) { - return new Dialog(new PanelContainer(document.getElementById(id), dockManager), dockManager, null); - } + this.eventListener = dockManager; - _initialize() { - this.panel.floatingDialog = this; - this.elementDialog = Object.assign(document.createElement('div'), { floatingDialog: this }); - this.elementDialog.tabIndex = 0; - this.elementDialog.appendChild(this.panel.elementPanel); - this.draggable = new DraggableContainer(this, this.panel, this.elementDialog, this.panel.elementTitle); + this.draggable = new DraggableContainer( + this, + panel, + this.element, + panel.elementTitle); - const resizeDirection: ResizeDirection = this.disableResize + const resizeDirection: ResizeDirection = disableResize ? ResizeDirection.None : ResizeDirection.All & ~ResizeDirection.NorthEast; - - this.resizable = new ResizableContainer(this, this.draggable, this.draggable.topLevelElement, resizeDirection); - this.dockManager.config.dialogRootElement.appendChild(this.elementDialog); - this.elementDialog.classList.add('dialog-floating'); + this.resizable = new ResizableContainer( + this, + this.draggable, + this.draggable.topLevelElement, + resizeDirection); - this.focusHandler = new EventHandler(this.elementDialog, 'focus', this.onFocus.bind(this), true); - this.mouseDownHandler = new EventHandler(this.elementDialog, 'pointerdown', this.onMouseDown.bind(this), true); - this.keyPressHandler = new EventHandler(this.elementDialog, 'keypress', this.dockManager.onKeyPressBound, true); + this.decoratedContainer = this.resizable; + } - this.resize(this.panel.elementPanel.clientWidth, this.panel.elementPanel.clientHeight); - this.isHidden = false; + public override initialize(): void { + this.grayoutParent?.grayOut(true); - if (this.grayoutParent != null) { - this.grayoutParent.grayOut(true); - } - this.bringToFront(); + super.initialize(); + + this.dockManager.context.model.dialogs.push(this); + this.position = this.dockManager.defaultDialogPosition; + this.dockManager.notifyOnCreateDialog(this); } - setPosition(x: number, y: number) { - let rect = this.dockManager.config.dialogRootElement.getBoundingClientRect(); - this.position = new Point(x - rect.left, y - rect.top); - this.elementDialog.style.left = (x - rect.left) + 'px'; - this.elementDialog.style.top = (y - rect.top) + 'px'; - this.panel.setDialogPosition(x, y); + public saveState(x: number, y: number): void { + this.position = new Point(x, y); this.dockManager.notifyOnChangeDialogPosition(this, x, y); } - getPosition(): Point { - return new Point(this.position ? this.position.x : 0, this.position ? this.position.y : 0); + public override setPosition(x: number, y: number): void { + super.setPosition(x, y); + this.dockManager.notifyOnChangeDialogPosition(this, x, y); } - onFocus() { - if (this.dockManager.activePanel != this.panel) - this.dockManager.activePanel = this.panel; + public getPosition(): Point { + return new Point(this.position ? this.position.x : 0, this.position ? this.position.y : 0); } - onMouseDown(e: PointerEvent) { - if (e.button != 2) - this.bringToFront(); + public override hide(): void { + super.hide(); + this.grayoutParent?.grayOut(false); } - destroy() { - this.panel.lastDialogSize = { width: this.resizable.width, height: this.resizable.height }; + public override destroy(): void { + super.destroy(); - if (this.focusHandler) { - this.focusHandler.cancel(); - delete this.focusHandler; - } - if (this.mouseDownHandler) { - this.mouseDownHandler.cancel(); - delete this.mouseDownHandler; - } - if (this.keyPressHandler) { - this.keyPressHandler.cancel(); - delete this.keyPressHandler; - } - - Utils.removeNode(this.elementDialog); this.draggable.removeDecorator(); - Utils.removeNode(this.panel.elementPanel); + this.resizable.removeDecorator(); Utils.arrayRemove(this.dockManager.context.model.dialogs, this); - this.panel.floatingDialog = undefined; - - if (this.grayoutParent) { - this.grayoutParent.grayOut(false); - } - } - - resize(width: number, height: number) { - this.resizable.resize(width, height); - } - - setTitle(title: string) { - this.panel.setTitle(title); - } - - setTitleIcon(iconName: string) { - this.panel.setTitleIcon(iconName); - } - - bringToFront() { - this.panel.elementContentContainer.style.zIndex = this.dockManager.zIndexDialogCounter++; - this.elementDialog.style.zIndex = this.dockManager.zIndexDialogCounter++; - this.dockManager.activePanel = this.panel; - } - - hide() { - this.elementDialog.style.zIndex = '0'; - this.panel.elementContentContainer.style.zIndex = ''; - this.elementDialog.style.display = 'none'; - if (!this.isHidden) { - this.isHidden = true; - this.dockManager.notifyOnHideDialog(this); - } - if (this.grayoutParent) { - this.grayoutParent.grayOut(false); - } } - close() { - this.hide(); - this.remove(); - this.dockManager.notifyOnClosePanel(this.panel); - this.destroy(); - } + public static fromElement(id: string, dockManager: DockManager): Dialog { + const dialog: Dialog = new Dialog( + new PanelContainer(document.getElementById(id), dockManager), + dockManager, + null); - remove() { - this.elementDialog.parentNode.removeChild(this.elementDialog); - } + dialog.initialize(); - show() { - this.panel.elementContentContainer.style.zIndex = this.dockManager.zIndexDialogCounter++; - this.elementDialog.style.zIndex = this.dockManager.zIndexDialogCounter++; - this.elementDialog.style.display = 'block'; - if (this.isHidden) { - this.isHidden = false; - this.dockManager.notifyOnShowDialog(this); - } + return dialog; } - static createContextMenuContentCallback = (dialog: Dialog, documentMangerNodes: DockNode[]): Node[] => { + public static createContextMenuContentCallback = (dialog: Dialog, documentMangerNodes: DockNode[]): Node[] => { if (!dialog.panel._hideCloseButton) { return []; } @@ -212,7 +125,19 @@ export class Dialog implements IContextMenuProvider { public createContextMenuItems(): Node[] { return Dialog.createContextMenuContentCallback( - this, + this, this.dockManager.context.model.documentManagerNode.children); } -} \ No newline at end of file + + protected override onShow(): void { + this.dockManager.notifyOnShowDialog(this); + } + + protected override onHide(): void { + this.dockManager.notifyOnHideDialog(this); + } + + public get elementDialog(): HTMLDivElement & { floatingPanel: FloatingPanel } { + return this.element; + } +} diff --git a/src/DockGraphDeserializer.ts b/src/DockGraphDeserializer.ts index b5d9b26..7050ee9 100644 --- a/src/DockGraphDeserializer.ts +++ b/src/DockGraphDeserializer.ts @@ -115,6 +115,8 @@ export class DockGraphDeserializer { Utils.removeNode(container.elementPanel); container.isDialog = true; let dialog = new Dialog(container, this.dockManager); + dialog.initialize(); + if (dialogInfo.position.x > document.body.clientWidth || dialogInfo.position.y > document.body.clientHeight - 70) { dialogInfo.position.x = 20; diff --git a/src/DockManager.ts b/src/DockManager.ts index df1513e..edb836d 100644 --- a/src/DockManager.ts +++ b/src/DockManager.ts @@ -208,7 +208,8 @@ export class DockManager { let offsetX = 0, offsetY = 0; for (let dialog of this.context.model.dialogs) { - if (dialog.position.x > this.element.clientWidth || dialog.position.y > this.element.clientHeight) { + const position = dialog.getPosition(); + if (position.x > this.element.clientWidth || position.y > this.element.clientHeight) { if (offsetX > this.element.clientWidth || offsetY > this.element.clientHeight) offsetX = 0, offsetY = 0; dialog.setPosition(100 + offsetX, 100 + offsetY); @@ -394,6 +395,7 @@ export class DockManager { Utils.removeNode(panel.elementPanel); panel.isDialog = true; let dialog = new Dialog(panel, this, grayoutParent, disableResize); + dialog.initialize(); dialog.setPosition(x, y); return dialog; } @@ -483,6 +485,7 @@ export class DockManager { // Create a new dialog window for the undocked panel let dialog = new Dialog(panelContainer, this, null); + dialog.initialize(); if (panelContainer.lastDialogSize) dialog.resize(panelContainer.lastDialogSize.width, panelContainer.lastDialogSize.height); @@ -522,6 +525,7 @@ export class DockManager { openInDialog(container: PanelContainer, event, dragOffset: Point, disableResize?: boolean) { // Create a new dialog window for the undocked panel let dialog = new Dialog(container, this, null, disableResize); + dialog.initialize(); if (event != null) { // Adjust the relative position diff --git a/src/FloatingPanel.ts b/src/FloatingPanel.ts new file mode 100644 index 0000000..d058e69 --- /dev/null +++ b/src/FloatingPanel.ts @@ -0,0 +1,173 @@ +import { DockManager } from './DockManager.js'; +import { EventHandler } from './EventHandler.js'; +import { IContextMenuProvider } from './interfaces/IContextMenuProvider.js'; +import { IDockContainer } from './interfaces/IDockContainer.js'; +import { PanelContainer } from './PanelContainer.js'; +import { Point } from './Point.js'; +import { Utils } from './Utils.js'; + +export abstract class FloatingPanel implements IContextMenuProvider { + public readonly panel: PanelContainer; + public isHidden: boolean; + + protected decoratedContainer: IDockContainer; + protected position: Point; + protected readonly dockManager: DockManager; + protected readonly keyPressHandler: EventHandler; + protected readonly focusHandler: EventHandler; + protected readonly mouseDownHandler: EventHandler; + + private _element: HTMLDivElement & { floatingPanel: FloatingPanel }; + + public constructor(panel: PanelContainer, dockManager: DockManager) { + this.panel = panel; + this.dockManager = dockManager; + this.panel.isDialog = true; + this.panel.floatingPanel = this; + this.isHidden = false; + this.decoratedContainer = this.panel; + + this._element = Object.assign(document.createElement('div'), { floatingPanel: this }); + this._element.tabIndex = 0; + this._element.appendChild(this.panel.elementPanel); + + this.focusHandler = new EventHandler( + this.element, + 'focus', + this.onFocus.bind(this), + true); + + this.mouseDownHandler = new EventHandler( + this.element, + 'pointerdown', + this.onMouseDown.bind(this), + true); + + this.keyPressHandler = new EventHandler( + this.element, + 'keypress', + this.dockManager.onKeyPressBound, + true); + } + + + public initialize(): void { + this.dockManager.config.dialogRootElement.appendChild(this.element); + this.element.classList.add('dialog-floating'); + + this.resize( + this.panel.elementPanel.clientWidth, + this.panel.elementPanel.clientHeight); + + this.bringToFront(); + } + + public abstract createContextMenuItems(): Node[]; + + public bringToFront(): void { + this.panel.elementContentContainer.style.zIndex = this.dockManager.zIndexDialogCounter++; + this.element.style.zIndex = this.dockManager.zIndexDialogCounter++; + this.dockManager.activePanel = this.panel; + } + + public show(): void { + this.panel.elementContentContainer.style.zIndex = this.dockManager.zIndexDialogCounter++; + this.element.style.zIndex = this.dockManager.zIndexDialogCounter++; + this.element.style.display = 'block'; + + if (this.isHidden) { + this.isHidden = false; + this.onShow(); + } + } + + public hide(): void { + this.element.style.zIndex = '0'; + this.panel.elementContentContainer.style.zIndex = ''; + this.element.style.display = 'none'; + + if (!this.isHidden) { + this.isHidden = true; + this.onHide(); + } + } + + public remove(): void { + Utils.removeNode(this.element); + } + + public close(): void { + this.hide(); + this.dockManager.notifyOnClosePanel(this.panel); + this.destroy(); + } + + public destroy(): void { + this.panel.lastDialogSize = { + width: this.decoratedContainer.width, + height: this.decoratedContainer.height + }; + + if (this.focusHandler) { + this.focusHandler.cancel(); + } + if (this.mouseDownHandler) { + this.mouseDownHandler.cancel(); + } + if (this.keyPressHandler) { + this.keyPressHandler.cancel(); + } + + Utils.removeNode(this.element); + Utils.removeNode(this.panel.elementPanel); + this.panel.floatingPanel = undefined; + } + + public resize(width: number, height: number): void { + this.decoratedContainer.resize(width, height); + } + + public setTitle(title: string): void { + this.panel.setTitle(title); + } + + public setTitleIcon(iconName: string): void { + this.panel.setTitleIcon(iconName); + } + + protected setPosition(x: number, y: number): void { + const rect: DOMRect = this.dockManager + .config + .dialogRootElement + .getBoundingClientRect(); + + this.position = new Point(x - rect.left, y - rect.top); + + this.element.style.left = `${this.position.x}px`; + this.element.style.top = `${this.position.y}px`; + this.panel.setDialogPosition(x, y); + } + + protected onShow(): void { + //Empty + } + + protected onHide(): void { + //Empty + } + + protected onFocus(): void { + if (this.dockManager.activePanel != this.panel) { + this.dockManager.activePanel = this.panel; + } + } + + protected onMouseDown(e: PointerEvent): void { + if (e.button != 2) + this.bringToFront(); + } + + public get element(): HTMLDivElement & { floatingPanel: FloatingPanel } { + return this._element; + } +} diff --git a/src/PanelContainer.ts b/src/PanelContainer.ts index db50eac..fcbfbeb 100644 --- a/src/PanelContainer.ts +++ b/src/PanelContainer.ts @@ -1,8 +1,8 @@ import { moveElementToNewBrowserWindow } from "./BrowserDialogHelper.js"; import { ContainerType } from "./ContainerType.js"; -import { Dialog } from "./Dialog.js"; import { DockManager } from "./DockManager.js"; import { EventHandler } from "./EventHandler.js"; +import { FloatingPanel } from "./FloatingPanel.js"; import { Point } from "./Point.js"; import { TabPage } from './TabPage.js'; import { UndockInitiator } from "./UndockInitiator.js"; @@ -54,7 +54,7 @@ export class PanelContainer implements IDockContainerWithSize, IContextMenuProvi lastDialogSize?: ISize; - _floatingDialog?: Dialog; + _floatingPanel?: FloatingPanel; _canUndock: boolean; _cachedWidth: number; _cachedHeight: number; @@ -84,7 +84,7 @@ export class PanelContainer implements IDockContainerWithSize, IContextMenuProvi this.elementContentContainer.addEventListener('pointerdown', (e) => { try { if (this.isDialog) { - this._floatingDialog.bringToFront(); + this._floatingPanel.bringToFront(); } else { if (this.tabPage) this.tabPage.setSelected(true, true); @@ -101,7 +101,7 @@ export class PanelContainer implements IDockContainerWithSize, IContextMenuProvi this.containerType = ContainerType.panel; this.icon = null; this.minimumAllowedChildNodes = 0; - this._floatingDialog = undefined; + this._floatingPanel = undefined; this.isDialog = false; this._canUndock = dockManager._undockEnabled; this.eventListeners = []; @@ -165,7 +165,7 @@ export class PanelContainer implements IDockContainerWithSize, IContextMenuProvi this._updateTitle(); this.undockInitiator = new UndockInitiator(this.elementTitle, this.performUndockToDialog.bind(this)); - this.floatingDialog = undefined; + this.floatingPanel = undefined; this.mouseDownHandler = new EventHandler(this.elementPanel, 'mousedown', this.onMouseDown.bind(this)); this.touchDownHandler = new EventHandler(this.elementPanel, 'touchstart', this.onMouseDown.bind(this), { passive: true }); @@ -255,12 +255,12 @@ export class PanelContainer implements IDockContainerWithSize, IContextMenuProvi this.eventListeners.splice(this.eventListeners.indexOf(listener), 1); } - get floatingDialog(): Dialog { - return this._floatingDialog; + get floatingPanel(): FloatingPanel { + return this._floatingPanel; } - set floatingDialog(value: Dialog) { - this._floatingDialog = value; - let canUndock = (this._floatingDialog === undefined); + set floatingPanel(value: FloatingPanel) { + this._floatingPanel = value; + let canUndock = (this._floatingPanel === undefined); this.undockInitiator.enabled = canUndock; this._contextMenuProvider = value ?? this; } @@ -592,9 +592,9 @@ export class PanelContainer implements IDockContainerWithSize, IContextMenuProvi this.dockManager.config.dialogRootElement.removeChild(this.elementContentContainer); if (this.isDialog) { - if (this.floatingDialog) { + if (this.floatingPanel) { //this.floatingDialog.hide(); - this.floatingDialog.close(); // fires onClose notification + this.floatingPanel.close(); // fires onClose notification } } else { diff --git a/src/ResizableContainer.ts b/src/ResizableContainer.ts index 2444a46..9f814a4 100644 --- a/src/ResizableContainer.ts +++ b/src/ResizableContainer.ts @@ -1,7 +1,7 @@ import { ContainerType } from "./ContainerType.js"; -import { Dialog } from "./Dialog.js"; import { DockManager } from "./DockManager.js"; import { EventHandler } from "./EventHandler.js"; +import { FloatingPanel } from "./FloatingPanel.js"; import { Point } from "./Point.js"; import { ResizeHandle } from "./ResizeHandle.js"; import { Utils } from "./Utils.js"; @@ -17,7 +17,7 @@ import { IThickness } from "./interfaces/IThickness.js"; export class ResizableContainer implements IDockContainer { topLevelElement: HTMLElement; - dialog: Dialog; + floatingPanel: FloatingPanel; delegate: IDockContainer; dockManager: DockManager; containerElement: HTMLElement; @@ -30,8 +30,8 @@ export class ResizableContainer implements IDockContainer { private iframeEventHandlers: EventHandler[]; private resizeDirection: ResizeDirection; - constructor(dialog: Dialog, delegate: IDockContainer, topLevelElement: HTMLElement, resizeDirection: ResizeDirection) { - this.dialog = dialog; + constructor(dialog: FloatingPanel, delegate: IDockContainer, topLevelElement: HTMLElement, resizeDirection: ResizeDirection) { + this.floatingPanel = dialog; this.resizeDirection = resizeDirection; this.delegate = delegate; this.containerElement = delegate.containerElement; @@ -183,8 +183,8 @@ export class ResizableContainer implements IDockContainer { return; this.readyToProcessNextResize = false; - if (this.dialog.panel) - this.dockManager.suspendLayout(this.dialog.panel); + if (this.floatingPanel.panel) + this.dockManager.suspendLayout(this.floatingPanel.panel); let currentMousePosition = new Point(touchOrMouseData.clientX, touchOrMouseData.clientY); if (iframeOffset) currentMousePosition = new Point(touchOrMouseData.clientX + iframeOffset.x, touchOrMouseData.clientY + iframeOffset.y); @@ -193,8 +193,8 @@ export class ResizableContainer implements IDockContainer { this._performDrag(handle, dx, dy); this.previousMousePosition = currentMousePosition; this.readyToProcessNextResize = true; - if (this.dialog.panel) - this.dockManager.resumeLayout(this.dialog.panel); + if (this.floatingPanel.panel) + this.dockManager.resumeLayout(this.floatingPanel.panel); this.dockManager.notifyOnContainerResized(this); }