Browse Source

be able to load and save custom blocks in NME

Pamela Wolf 5 years ago
parent
commit
868adb5c0a

File diff suppressed because it is too large
+ 10 - 0
nodeEditor/imgs/add.svg


File diff suppressed because it is too large
+ 10 - 0
nodeEditor/imgs/delete.svg


+ 57 - 0
nodeEditor/src/components/nodeList/nodeList.scss

@@ -75,6 +75,19 @@
                         color: white;
                     }
                 }
+
+                .withButton {
+                    height: 40px;
+                    position: relative;
+                    fill: white;
+                    .icon {
+                        position: absolute;
+                        right: 10px;
+                        top: 10px;
+                        fill: white;
+                    }
+                    
+                }
             
                 .buttonLine {
                     height: 30px;
@@ -103,6 +116,50 @@
                     }  
                 }
 
+                .buttonIcon {
+                    position: relative;
+                    .icon {
+                        position: absolute;
+                        right: 20px;
+                        top:  3px;
+                    }
+
+                    height: 30px;
+                    display: grid;
+                    align-items: center;
+                    justify-items: stretch;
+                    padding-bottom: 5px;
+            
+                    input[type="file"] {
+                        display: none;
+                    }
+            
+                    .file-upload {    
+                        position: absolute;           
+                        background: transparent;
+                        border: 1px transparent;
+                        margin: 5px 10px;
+                        color:transparent;
+                        padding: 15px 5px 0px 15px;
+                        opacity: 0.9;
+                        cursor: pointer;
+                        text-align: center;
+                        display: inline-block;
+                        z-index: 1;
+                        right: 20px;
+                        top:  2px;
+                    }
+            
+                    .file-upload:hover {
+                        opacity: 0.0;
+                    }
+            
+                    .file-upload:active {
+                        transform: scale(0.98);
+                        transform-origin: 0.5 0.5;
+                    }
+                }
+
                 .paneContainer {
                     margin-top: 3px;
                     display:grid;

+ 80 - 2
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -6,6 +6,9 @@ import { DraggableLineComponent } from '../../sharedComponents/draggableLineComp
 import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
 import { Observer } from 'babylonjs/Misc/observable';
 import { Nullable } from 'babylonjs/types';
+import { DraggableLineWithButtonComponent } from '../../sharedComponents/draggableLineWithButtonComponent';
+import { LineWithFileButtonContainerComponent } from '../../sharedComponents/lineWithFileButtonContainerComponent';
+import { Tools } from 'babylonjs';
 
 require("./nodeList.scss");
 
@@ -144,12 +147,20 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "FragCoordBlock": "The gl_FragCoord predefined variable that contains the window relative coordinate (x, y, z, 1/w)",
         "ScreenSizeBlock": "The size (in pixels) of the screen window",
     };
+    
+    customFrameList: string[] | null;
 
     constructor(props: INodeListComponentProps) {
         super(props);
 
         this.state = { filter: "" };
 
+        let frameJson = localStorage.getItem("Custom-Frame-List");
+        if(frameJson) {
+            this.customFrameList = JSON.parse(frameJson);
+        }
+
+
         this._onResetRequiredObserver = this.props.globalState.onResetRequiredObservable.add(() => {
             this.forceUpdate();
         });
@@ -163,10 +174,62 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         this.setState({ filter: filter });
     }
 
+    loadFrameBlock(file: File) {
+        Tools.ReadFile(file, async (data) => {
+            // get Frame Data from file
+            let decoder = new TextDecoder("utf-8");
+            const frameData = JSON.parse(decoder.decode(data));
+            let frameName = file.name.replace(".json","") + "Custom";
+
+            try {
+                localStorage.setItem(frameName, JSON.stringify(frameData));
+            } catch (error) {
+                this.props.globalState.onErrorMessageDialogRequiredObservable.notifyObservers("Error Saving Frame");
+                return;
+            }
+
+            let frameJson = localStorage.getItem("Custom-Frame-List");
+            if(frameJson) {
+                let frameList = JSON.parse(frameJson);
+                let index = frameList.findIndex((element: string) => element === frameName);
+                if( index === -1){
+                    frameList[frameList.length] = frameName;
+                    localStorage.setItem("Custom-Frame-List", JSON.stringify(frameList));
+                    this.customFrameList = frameList;
+                    this.forceUpdate();
+                }
+            }
+            else {
+                let newframeList: string[] = [frameName];
+                let newFrameJson = JSON.stringify(newframeList);
+                localStorage.setItem("Custom-Frame-List", newFrameJson);
+                this.customFrameList = newframeList;
+                this.forceUpdate();
+            }
+
+        
+        }, undefined, true);
+    }
+
+    removeItem(value : string) : void {
+        let frameJson = localStorage.getItem("Custom-Frame-List");
+            if(frameJson) {
+                let frameList = JSON.parse(frameJson);
+                let index = frameList.findIndex((element: string) => element === value);
+                frameList.splice(index, 1);
+                localStorage.removeItem(value);
+                localStorage.setItem("Custom-Frame-List", JSON.stringify(frameList));
+                this.customFrameList = frameList;
+                this.forceUpdate();
+            }
+           
+    }
+
     render() {
+
         // Block types used to create the menu from
         const allBlocks = {
-
+            Custom_Blocks: this.customFrameList === null ? [""] : this.customFrameList,
             Animation: ["BonesBlock", "MorphTargetsBlock"],
             Color_Management: ["ReplaceColorBlock", "PosterizeBlock", "GradientBlock", "DesaturateBlock"],
             Conversion_Blocks: ["ColorMergerBlock", "ColorSplitterBlock", "VectorMergerBlock", "VectorSplitterBlock"],
@@ -213,10 +276,25 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             .map((block: any, i: number) => {
                 let tooltip = NodeListComponent._Tooltips[block] || "";
 
+                
+                if(key.indexOf("Custom") === -1) {
                 return <DraggableLineComponent key={block} data={block} tooltip={tooltip}/>;
+                }
+                return <DraggableLineWithButtonComponent key={block} data={block} tooltip={tooltip} 
+                onIconClick={ value => this.removeItem(value)}/>;
             });
 
-            if (blockList.length) {
+            if(key === "Custom_Blocks") {
+                blockMenu.push(
+                    <LineWithFileButtonContainerComponent key={key + " blocks"} title={key.replace("__", ": ").replace("_", " ")} closed={false}
+                    label="Load Custom" uploadName={'custom-frame-upload2'}  accept=".json" onClick={(file) => {
+                        this.loadFrameBlock(file);
+                    }}>
+                        {blockList}
+                    </LineWithFileButtonContainerComponent>
+                );
+            }
+            else if(blockList.length) {
                 blockMenu.push(
                     <LineContainerComponent key={key + " blocks"} title={key.replace("__", ": ").replace("_", " ")} closed={false}>
                         {blockList}

+ 12 - 1
nodeEditor/src/graphEditor.tsx

@@ -545,9 +545,20 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
         var data = event.dataTransfer.getData("babylonjs-material-node") as string;
         let newNode: GraphNode;
 
+        if(data.indexOf("Custom") > -1) {
+            let storageData = localStorage.getItem(data);
+            if(storageData) {   
+                const frameData = JSON.parse(storageData);
+                SerializationTools.AddFrameToMaterial(frameData, this.props.globalState, this.props.globalState.nodeMaterial);
+                this.forceUpdate();
+                return;
+            }
+        }
+
         if (data.indexOf("Block") === -1) {
             newNode = this.addValueNode(data);
-        } else {
+        } 
+        else {
             let block = BlockTools.GetBlockFromString(data, this.props.globalState.nodeMaterial.getScene(), this.props.globalState.nodeMaterial)!;   
             
             if (block.isUnique) {

+ 32 - 0
nodeEditor/src/sharedComponents/draggableLineWithButtonComponent.tsx

@@ -0,0 +1,32 @@
+import * as React from "react";
+const deleteButton = require('../../imgs/delete.svg');
+
+export interface IDraggableLineWithButtonComponent {
+    data: string;
+    tooltip: string;
+    onIconClick: (value: string) => void;
+}
+
+export class DraggableLineWithButtonComponent extends React.Component<IDraggableLineWithButtonComponent> {
+    constructor(props: IDraggableLineWithButtonComponent) {
+        super(props);
+    }
+
+    render() {
+
+        
+        return (
+            <div className="draggableLine withButton" 
+                title={this.props.tooltip}
+                draggable={true}
+                onDragStart={event => {
+                    event.dataTransfer.setData("babylonjs-material-node", this.props.data);
+                }}>
+                {this.props.data.substr(0, this.props.data.length - 6)}
+                <div className="icon" onClick={() => { this.props.onIconClick(this.props.data); }} title="Delete">
+                    <img title="Delete" className="deleteIcon" src={deleteButton}/>
+                </div>
+            </div>
+        );
+    }
+}

+ 99 - 0
nodeEditor/src/sharedComponents/lineWithFileButtonContainerComponent.tsx

@@ -0,0 +1,99 @@
+import * as React from "react";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
+import { DataStorage } from 'babylonjs/Misc/dataStorage';
+const addButton = require('../../imgs/add.svg');
+
+interface ILineWithFileButtonContainerComponentProps {
+    title: string;
+    children: any[] | any;
+    closed?: boolean;
+    label: string;
+    onClick: (file: File) => void;
+    accept: string;
+    uploadName?: string;
+}
+
+export class LineWithFileButtonContainerComponent extends React.Component<ILineWithFileButtonContainerComponentProps, { isExpanded: boolean }> {
+    private uploadRef: React.RefObject<HTMLInputElement>
+    constructor(props: ILineWithFileButtonContainerComponentProps) {
+        super(props);
+
+        let initialState = DataStorage.ReadBoolean(this.props.title, !this.props.closed);
+
+        this.state = { isExpanded: initialState };
+        this.uploadRef = React.createRef();
+    }
+
+    onChange(evt: any) {
+        var files: File[] = evt.target.files;
+        if (files && files.length) {
+            this.props.onClick(files[0]);
+        }
+
+        evt.target.value = "";
+    }
+
+
+    switchExpandedState(): void {
+        const newState = !this.state.isExpanded;
+
+        DataStorage.WriteBoolean(this.props.title, newState);
+
+        this.setState({ isExpanded: newState });
+    }
+
+    renderHeader() {
+        let className = this.state.isExpanded ? "collapse" : "collapse closed";
+
+        return (
+            <div className="header" onClick={() => this.switchExpandedState()}>
+                <div className="title">
+                    {this.props.title}
+                </div>
+                <div className="buttonIcon" title="Upload Custom">
+                <label htmlFor={this.props.uploadName ? this.props.uploadName : "file-upload"} className="file-upload">
+                        </label>
+                    <div className="icon">
+                    <img title="Add" className="addIcon" src={addButton}/>
+                    </div>
+                    <div className="icon">
+                    <input ref={this.uploadRef} id={this.props.uploadName ? this.props.uploadName : "file-upload"} type="file" accept={this.props.accept} onChange={evt => this.onChange(evt)} />
+                    </div>
+                    <div className={className}>
+                    <FontAwesomeIcon icon={faChevronDown} />
+                    </div>
+                </div>
+               
+            </div>
+        );
+    }
+    
+    render() {
+        
+        if (!this.state.isExpanded) {
+            return (
+                <div className="paneContainer">
+                    <div className="paneContainer-content">
+                        {
+                            this.renderHeader()
+                        }
+                    </div>
+                </div>
+            );
+        }
+
+        return (
+            <div className="paneContainer">
+                <div className="paneContainer-content">
+                    {
+                        this.renderHeader()
+                    }
+                    <div className="paneList">
+                        {this.props.children}
+                    </div >
+                </div>
+            </div>
+        );
+    }
+}