import * as React from "react"; import { GlobalState } from './globalState'; import { NodeListComponent } from './components/nodeList/nodeListComponent'; import { PropertyTabComponent } from './components/propertyTab/propertyTabComponent'; import { Portal } from './portal'; import { LogComponent, LogEntry } from './components/log/logComponent'; import { DataStorage } from 'babylonjs/Misc/dataStorage'; import { Nullable } from 'babylonjs/types'; import { MessageDialogComponent } from './sharedComponents/messageDialog'; import { BlockTools } from './blockTools'; import { IEditorData } from './nodeLocationInfo'; import { WorkbenchComponent } from './diagram/workbench'; import { GUINode } from './diagram/guiNode'; import { IInspectorOptions } from "babylonjs/Debug/debugLayer"; import { _TypeStore } from 'babylonjs/Misc/typeStore'; require("./main.scss"); interface IGraphEditorProps { globalState: GlobalState; } interface IGraphEditorState { showPreviewPopUp: boolean; }; interface IInternalPreviewAreaOptions extends IInspectorOptions { popup: boolean; original: boolean; explorerWidth?: string; inspectorWidth?: string; embedHostWidth?: string; } export class WorkbenchEditor extends React.Component { private _workbenchCanvas: WorkbenchComponent; private _startX: number; private _moveInProgress: boolean; private _leftWidth = DataStorage.ReadNumber("LeftWidth", 200); private _rightWidth = DataStorage.ReadNumber("RightWidth", 300); private _blocks = new Array(); private _onWidgetKeyUpPointer: any; private _previewHost: Nullable; private _popUpWindow: Window; /** * Creates a node and recursivly creates its parent nodes from it's input * @param block */ public createNodeFromObject(block: BABYLON.GUI.Control, recursion = true) { if (this._blocks.indexOf(block) !== -1) { return this._workbenchCanvas.nodes.filter(n => n.guiNode === block)[0]; } this._blocks.push(block); // Graph const node = null;// this._workbenchCanvas.appendBlock(block); return node; } componentDidMount() { if (this.props.globalState.hostDocument) { this._workbenchCanvas = (this.refs["graphCanvas"] as WorkbenchComponent); // this._previewManager = new PreviewManager(this.props.globalState.hostDocument.getElementById("preview-canvas") as HTMLCanvasElement, this.props.globalState); } if (navigator.userAgent.indexOf("Mobile") !== -1) { ((this.props.globalState.hostDocument || document).querySelector(".blocker") as HTMLElement).style.visibility = "visible"; } } componentWillUnmount() { if (this.props.globalState.hostDocument) { this.props.globalState.hostDocument!.removeEventListener("keyup", this._onWidgetKeyUpPointer, false); } } constructor(props: IGraphEditorProps) { super(props); this.state = { showPreviewPopUp: false }; this.props.globalState.onRebuildRequiredObservable.add(() => { }); this.props.globalState.onResetRequiredObservable.add(() => { }); this.props.globalState.onImportFrameObservable.add((source: any) => { /*const frameData = source.editorData.frames[0]; // create new graph nodes for only blocks from frame (last blocks added) this.props.globalState.nodeMaterial.attachedBlocks.slice(-(frameData.blocks.length)).forEach((block: NodeMaterialBlock) => { this.createNodeFromObject(); }); this.reOrganize(this.props.globalState.nodeMaterial.editorData, true);*/ }) this.props.globalState.onZoomToFitRequiredObservable.add(() => { this.zoomToFit(); }); this.props.globalState.onReOrganizedRequiredObservable.add(() => { this.reOrganize(); }); this.props.globalState.hostDocument!.addEventListener("keydown", evt => { if ((evt.keyCode === 46 || evt.keyCode === 8) && !this.props.globalState.blockKeyboardEvents) { // Delete let selectedItems = this._workbenchCanvas.selectedGuiNodes; for (var selectedItem of selectedItems) { selectedItem.dispose(); //let targetBlock = selectedItem.block; //this.props.globalState.nodeMaterial!.removeBlock(targetBlock); //let blockIndex = this._blocks.indexOf(targetBlock); //if (blockIndex > -1) { // this._blocks.splice(blockIndex, 1); //} } this.props.globalState.onSelectionChangedObservable.notifyObservers(null); this.props.globalState.onRebuildRequiredObservable.notifyObservers(); return; } if (!evt.ctrlKey || this.props.globalState.blockKeyboardEvents) { return; } if (evt.key === "c") { // Copy let selectedItems = this._workbenchCanvas.selectedGuiNodes; if (!selectedItems.length) { return; } let selectedItem = selectedItems[0] as GUINode; if (!selectedItem.guiNode) { return; } } else if (evt.key === "v") { // Paste //const rootElement = this.props.globalState.hostDocument!.querySelector(".diagram-container") as HTMLDivElement; //const zoomLevel = this._workbenchCanvas.zoom; //let currentY = (this._mouseLocationY - rootElement.offsetTop - this._workbenchCanvas.y - 20) / zoomLevel; } }, false); } pasteSelection(copiedNodes: GUINode[], currentX: number, currentY: number, selectNew = false) { //let originalNode: Nullable = null; let newNodes:GUINode[] = []; // Copy to prevent recursive side effects while creating nodes. copiedNodes = copiedNodes.slice(); // Cancel selection this.props.globalState.onSelectionChangedObservable.notifyObservers(null); // Create new nodes for (var node of copiedNodes) { let block = node.guiNode; if (!block) { continue; } let clone = null;//block.clone(this.props.globalState.nodeMaterial.getScene()); if (!clone) { return; } //let newNode = this.createNodeFromObject(clone, false); /*let x = 0; let y = 0; if (originalNode) { x = currentX + node.x - originalNode.x; y = currentY + node.y - originalNode.y; } else { originalNode = node; x = currentX; y = currentY; }*/ /*newNode.x = x; newNode.y = y; newNode.cleanAccumulation(); newNodes.push(newNode); if (selectNew) { this.props.globalState.onSelectionChangedObservable.notifyObservers(newNode); }*/ } return newNodes; } zoomToFit() { this._workbenchCanvas.zoomToFit(); } buildMaterial() { this.props.globalState.onLogRequiredObservable.notifyObservers(new LogEntry("Node material build successful", false)); this.props.globalState.onBuiltObservable.notifyObservers(); } showWaitScreen() { this.props.globalState.hostDocument.querySelector(".wait-screen")?.classList.remove("hidden"); } hideWaitScreen() { this.props.globalState.hostDocument.querySelector(".wait-screen")?.classList.add("hidden"); } reOrganize(editorData: Nullable = null, isImportingAFrame = false) { this.showWaitScreen(); this._workbenchCanvas._isLoading = true; // Will help loading large graphes setTimeout(() => { if (!editorData || !editorData.locations) { this._workbenchCanvas.distributeGraph(); } else { // Locations for (var location of editorData.locations) { for (var node of this._workbenchCanvas.nodes) { if (node.guiNode && node.guiNode.uniqueId === location.blockId) { node.x = location.x; node.y = location.y; node.cleanAccumulation(); break; } } } if (!isImportingAFrame){ this._workbenchCanvas.processEditorData(editorData); } } this._workbenchCanvas._isLoading = false; for (var node of this._workbenchCanvas.nodes) { } this.hideWaitScreen(); }); } onPointerDown(evt: React.PointerEvent) { this._startX = evt.clientX; this._moveInProgress = true; evt.currentTarget.setPointerCapture(evt.pointerId); } onPointerUp(evt: React.PointerEvent) { this._moveInProgress = false; evt.currentTarget.releasePointerCapture(evt.pointerId); } resizeColumns(evt: React.PointerEvent, forLeft = true) { if (!this._moveInProgress) { return; } const deltaX = evt.clientX - this._startX; const rootElement = evt.currentTarget.ownerDocument!.getElementById("node-editor-graph-root") as HTMLDivElement; if (forLeft) { this._leftWidth += deltaX; this._leftWidth = Math.max(150, Math.min(400, this._leftWidth)); DataStorage.WriteNumber("LeftWidth", this._leftWidth); } else { this._rightWidth -= deltaX; this._rightWidth = Math.max(250, Math.min(500, this._rightWidth)); DataStorage.WriteNumber("RightWidth", this._rightWidth); rootElement.ownerDocument!.getElementById("preview")!.style.height = this._rightWidth + "px"; } rootElement.style.gridTemplateColumns = this.buildColumnLayout(); this._startX = evt.clientX; } buildColumnLayout() { return `${this._leftWidth}px 4px calc(100% - ${this._leftWidth + 8 + this._rightWidth}px) 4px ${this._rightWidth}px`; } emitNewBlock(event: React.DragEvent) { var data = event.dataTransfer.getData("babylonjs-material-node") as string; let guiElement = BlockTools.GetGuiFromString(data); //guiElement.background = "#138016FF"; let newGuiNode = this._workbenchCanvas.appendBlock(guiElement); /*let x = event.clientX;// - event.currentTarget.offsetLeft - this._workbenchCanvas.x; let y = event.clientY;// - event.currentTarget.offsetTop - this._workbenchCanvas.y - 20; newGuiNode.x += (x - newGuiNode.x); newGuiNode.y += y - newGuiNode.y; //newGuiNode.cleanAccumulation();*/ this.props.globalState.onSelectionChangedObservable.notifyObservers(null); this.props.globalState.onSelectionChangedObservable.notifyObservers(newGuiNode); this.forceUpdate(); } handlePopUp = () => { this.setState({ showPreviewPopUp : true }); this.createPopUp(); this.props.globalState.hostWindow.addEventListener('beforeunload', this.handleClosingPopUp); } handleClosingPopUp = () => { this._popUpWindow.close(); this.setState({ showPreviewPopUp: false }, () => this.initiatePreviewArea() ); } initiatePreviewArea = (canvas: HTMLCanvasElement = this.props.globalState.hostDocument.getElementById("preview-canvas") as HTMLCanvasElement) => { //this._previewManager = new PreviewManager(canvas, this.props.globalState); } createPopUp = () => { const userOptions = { original: true, popup: true, overlay: false, embedMode: false, enableClose: true, handleResize: true, enablePopup: true, }; const options = { embedHostWidth: "100%", ...userOptions }; const popUpWindow = this.createPopupWindow("PREVIEW AREA", "_PreviewHostWindow"); if (popUpWindow) { popUpWindow.addEventListener('beforeunload', this.handleClosingPopUp); const parentControl = popUpWindow.document.getElementById('gui-editor-workbench-root'); this.createPreviewMeshControlHost(options, parentControl); this.createPreviewHost(options, parentControl); if (parentControl) { this.fixPopUpStyles(parentControl.ownerDocument!); this.initiatePreviewArea(parentControl.ownerDocument!.getElementById("preview-canvas") as HTMLCanvasElement); } } } createPopupWindow = (title: string, windowVariableName: string, width = 500, height = 500): Window | null => { const windowCreationOptionsList = { width: width, height: height, top: (this.props.globalState.hostWindow.innerHeight - width) / 2 + window.screenY, left: (this.props.globalState.hostWindow.innerWidth - height) / 2 + window.screenX }; var windowCreationOptions = Object.keys(windowCreationOptionsList) .map( (key) => key + '=' + (windowCreationOptionsList as any)[key] ) .join(','); const popupWindow = this.props.globalState.hostWindow.open("", title, windowCreationOptions); if (!popupWindow) { return null; } const parentDocument = popupWindow.document; parentDocument.title = title; parentDocument.body.style.width = "100%"; parentDocument.body.style.height = "100%"; parentDocument.body.style.margin = "0"; parentDocument.body.style.padding = "0"; let parentControl = parentDocument.createElement("div"); parentControl.style.width = "100%"; parentControl.style.height = "100%"; parentControl.style.margin = "0"; parentControl.style.padding = "0"; parentControl.style.display = "grid"; parentControl.style.gridTemplateRows = "40px auto"; parentControl.id = 'node-editor-graph-root'; parentControl.className = 'right-panel'; popupWindow.document.body.appendChild(parentControl); this.copyStyles(this.props.globalState.hostWindow.document, parentDocument); (this as any)[windowVariableName] = popupWindow; this._popUpWindow = popupWindow; return popupWindow; } copyStyles = (sourceDoc: HTMLDocument, targetDoc: HTMLDocument) => { const styleContainer = []; for (var index = 0; index < sourceDoc.styleSheets.length; index++) { var styleSheet: any = sourceDoc.styleSheets[index]; try { if (styleSheet.href) { // for elements loading CSS from a URL const newLinkEl = sourceDoc.createElement('link'); newLinkEl.rel = 'stylesheet'; newLinkEl.href = styleSheet.href; targetDoc.head!.appendChild(newLinkEl); styleContainer.push(newLinkEl); } else if (styleSheet.cssRules) { // for