Ver código fonte

Implement NME Preview Redesign

Kyle Belfort 5 anos atrás
pai
commit
a601dca7da

+ 1 - 0
dist/preview release/what's new.md

@@ -111,6 +111,7 @@
 - Added preview area pop up for NME ([Kyle Belfort](https://github.com/belfortk))
 - Added comments to frames in NME ([Kyle Belfort](https://github.com/belfortk))
 - Make frames resizable in NME ([Kyle Belfort](https://github.com/belfortk))
+- Implement NME Preview Area Redesign ([Kyle Belfort](https://github.com/belfortk))
 
 ### Meshes
 

+ 28 - 55
nodeEditor/src/components/preview/previewAreaComponent.tsx

@@ -1,10 +1,12 @@
 
 import * as React from "react";
 import { GlobalState } from '../../globalState';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faPlay, faStop, faPalette, faCheckDouble, faSun, faLocationArrow, faClone } from '@fortawesome/free-solid-svg-icons';
-import { Color3, Color4 } from 'babylonjs/Maths/math.color';
 import { DataStorage } from '../../dataStorage';
+import DoubleSided from './svgs/doubleSided.svg'
+import DepthPass from './svgs/depthPass.svg'
+import Omni from './svgs/omni.svg'
+import DirectionalRight from './svgs/directionalRight.svg'
+import DirectionalLeft from './svgs/directionalLeft.svg'
 
 interface IPreviewAreaComponentProps {
     globalState: GlobalState;
@@ -21,23 +23,6 @@ export class PreviewAreaComponent extends React.Component<IPreviewAreaComponentP
         this.props.globalState.onIsLoadingChanged.add(state => this.setState({isLoading: state}));
     }
 
-    changeAnimation() {
-        this.props.globalState.rotatePreview = !this.props.globalState.rotatePreview;
-        this.props.globalState.onAnimationCommandActivated.notifyObservers();
-        this.forceUpdate();
-    }
-
-    changeBackground(value: string) {
-        const newColor = Color3.FromHexString(value);
-
-        DataStorage.StoreNumber("BackgroundColorR", newColor.r);
-        DataStorage.StoreNumber("BackgroundColorG", newColor.g);
-        DataStorage.StoreNumber("BackgroundColorB", newColor.b);
-
-        this.props.globalState.backgroundColor = Color4.FromColor3(newColor, 1.0);
-        this.props.globalState.onPreviewBackgroundChanged.notifyObservers();
-    }
-
     changeBackFaceCulling(value: boolean) {        
         this.props.globalState.backFaceCulling = value;
         DataStorage.StoreBoolean("BackFaceCulling", value);
@@ -63,30 +48,17 @@ export class PreviewAreaComponent extends React.Component<IPreviewAreaComponentP
                         </div>
                     }
                 </div>                
-                <div id="preview-config-bar">
-                    <div                     
-                        title="Turn-table animation"
-                        onClick={() => this.changeAnimation()} className={"button"}>
-                        <FontAwesomeIcon icon={this.props.globalState.rotatePreview ? faStop : faPlay} />
-                    </div>
-                    <div 
-                        title="Background color"
-                        className={"button align"}>
-                        <label htmlFor="color-picker" id="color-picker-label">
-                            <FontAwesomeIcon icon={faPalette} />
-                        </label>
-                        <input ref="color-picker" id="color-picker" type="color" onChange={evt => this.changeBackground(evt.target.value)} />
-                    </div>                        
+                <div id="preview-config-bar">              
                     <div
                         title="Render without back face culling"
-                        onClick={() => this.changeBackFaceCulling(!this.props.globalState.backFaceCulling)} className={"button" + (!this.props.globalState.backFaceCulling ? " selected" : "")}>
-                        <FontAwesomeIcon icon={faCheckDouble} />
-                    </div>  
+                        onClick={() => this.changeBackFaceCulling(!this.props.globalState.backFaceCulling)} className={"button back-face" + (!this.props.globalState.backFaceCulling ? " selected" : "")}>
+                        <img src={DoubleSided} alt=""/>
+                    </div>
                     <div
                         title="Render with depth pre-pass"
-                        onClick={() => this.changeDepthPrePass(!this.props.globalState.depthPrePass)} className={"button" + (this.props.globalState.depthPrePass ? " selected" : "")}>
-                        <FontAwesomeIcon icon={faClone} />
-                    </div>                     
+                        onClick={() => this.changeDepthPrePass(!this.props.globalState.depthPrePass)} className={"button depth-pass" + (this.props.globalState.depthPrePass ? " selected" : "")}>
+                            <img src={DepthPass} alt=""/>
+                    </div>
                     <div
                         title="Turn on/off hemispheric light"  
                         onClick={() => {
@@ -94,29 +66,30 @@ export class PreviewAreaComponent extends React.Component<IPreviewAreaComponentP
                             DataStorage.StoreBoolean("HemisphericLight", this.props.globalState.hemisphericLight);
                             this.props.globalState.onLightUpdated.notifyObservers();
                             this.forceUpdate();
-                        }} className={"button" + (this.props.globalState.hemisphericLight ? " selected" : "")}>
-                        <FontAwesomeIcon icon={faSun} />
-                    </div>    
+                        }} className={"button hemispheric-light" + (this.props.globalState.hemisphericLight ? " selected" : "")}>
+                        <img src={Omni} alt=""/>
+                    </div>
                     <div
-                        title="Turn on/off direction light #0"  
+                        title="Turn on/off direction light #1"  
                         onClick={() => {
-                            this.props.globalState.directionalLight0 = !this.props.globalState.directionalLight0;                       
-                            DataStorage.StoreBoolean("DirectionalLight0", this.props.globalState.directionalLight0);
+                            this.props.globalState.directionalLight1 = !this.props.globalState.directionalLight1;                       
+                            DataStorage.StoreBoolean("DirectionalLight1", this.props.globalState.directionalLight1);
                             this.props.globalState.onLightUpdated.notifyObservers();
                             this.forceUpdate();
-                        }} className={"button" + (this.props.globalState.directionalLight0 ? " selected" : "")}>
-                        <FontAwesomeIcon icon={faLocationArrow} />
-                    </div>      
+                        }} className={"button direction-light-1" + (this.props.globalState.directionalLight1 ? " selected" : "")}>
+                        <img src={DirectionalRight} alt=""/>
+
+                    </div>
                     <div
-                        title="Turn on/off direction light #1"  
+                        title="Turn on/off direction light #0"  
                         onClick={() => {
-                            this.props.globalState.directionalLight1 = !this.props.globalState.directionalLight1;                       
-                            DataStorage.StoreBoolean("DirectionalLight1", this.props.globalState.directionalLight1);
+                            this.props.globalState.directionalLight0 = !this.props.globalState.directionalLight0;                       
+                            DataStorage.StoreBoolean("DirectionalLight0", this.props.globalState.directionalLight0);
                             this.props.globalState.onLightUpdated.notifyObservers();
                             this.forceUpdate();
-                        }} className={"button" + (this.props.globalState.directionalLight1 ? " selected" : "")}>
-                        <FontAwesomeIcon icon={faLocationArrow} />
-                    </div>               
+                        }} className={"button direction-light-0" + (this.props.globalState.directionalLight0 ? " selected" : "")}>
+                        <img src={DirectionalLeft} alt=""/>
+                    </div>
                 </div>
             </>
         );

+ 53 - 7
nodeEditor/src/components/preview/previewMeshControlComponent.tsx

@@ -1,12 +1,15 @@
 
 import * as React from "react";
 import { GlobalState } from '../../globalState';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faWindowRestore } from '@fortawesome/free-solid-svg-icons';
+import { Color3, Color4 } from 'babylonjs/Maths/math.color';
 import { PreviewMeshType } from './previewMeshType';
 import { DataStorage } from '../../dataStorage';
 import { OptionsLineComponent } from '../../sharedComponents/optionsLineComponent';
 import * as ReactDOM from 'react-dom';
+import PopUpIcon from './svgs/popOut.svg'
+import ColorPicker from './svgs/colorPicker.svg';
+import PauseIcon from './svgs/pauseIcon.svg';
+import PlayIcon from './svgs/playIcon.svg'
 
 interface IPreviewMeshControlComponent {
     globalState: GlobalState;
@@ -14,6 +17,12 @@ interface IPreviewMeshControlComponent {
 }
 
 export class PreviewMeshControlComponent extends React.Component<IPreviewMeshControlComponent> {
+    private colorInputRef: React.RefObject<HTMLInputElement>;
+
+    constructor(props: IPreviewMeshControlComponent) {
+        super(props);
+        this.colorInputRef = React.createRef();
+    }
 
     changeMeshType(newOne: PreviewMeshType) {
         if (this.props.globalState.previewMeshType === newOne) {
@@ -45,6 +54,28 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
         this.props.togglePreviewAreaComponent();
     }
 
+    changeAnimation() {
+        this.props.globalState.rotatePreview = !this.props.globalState.rotatePreview;
+        this.props.globalState.onAnimationCommandActivated.notifyObservers();
+        this.forceUpdate();
+    }
+
+    changeBackground(value: string) {
+        const newColor = Color3.FromHexString(value);
+
+        DataStorage.StoreNumber("BackgroundColorR", newColor.r);
+        DataStorage.StoreNumber("BackgroundColorG", newColor.g);
+        DataStorage.StoreNumber("BackgroundColorB", newColor.b);
+
+        const newBackgroundColor = Color4.FromColor3(newColor, 1.0);
+        this.props.globalState.backgroundColor = newBackgroundColor;
+        this.props.globalState.onPreviewBackgroundChanged.notifyObservers();
+    }
+
+    changeBackgroundClick() {
+        this.colorInputRef.current?.click();
+    }
+
     render() {
 
         var meshTypeOptions = [
@@ -73,19 +104,34 @@ export class PreviewMeshControlComponent extends React.Component<IPreviewMeshCon
                                 } else {
                                     (ReactDOM.findDOMNode(this.refs["file-picker"]) as HTMLElement).click();
                                 }
-                            }} />    
+                            }} />
                 <div style={{
                     display: "none"
                 }} title="Preview with a custom mesh" >
                     <input ref="file-picker" id="file-picker" type="file" onChange={evt => this.useCustomMesh(evt)} accept=".gltf, .glb, .babylon, .obj"/>
                 </div>
                 <div
+                    title="Turn-table animation"
+                    onClick={() => this.changeAnimation()} className="button" id="play-button">
+                    {this.props.globalState.rotatePreview ? <img src={PauseIcon} alt=""/> : <img src={PlayIcon} alt=""/>}
+                </div>
+                <div 
+                id="color-picker-button"
+                    title="Background color"
+                    className={"button align"}
+                    onClick={_ => this.changeBackgroundClick()}
+                    >
+                    <img src={ColorPicker} alt=""/>
+                    <label htmlFor="color-picker" id="color-picker-label">
+                    </label>
+                    <input ref={this.colorInputRef} id="color-picker" type="color" onChange={evt => this.changeBackground(evt.target.value)} />
+                </div>
+                <div
                     title="Open preview in new window" id="preview-new-window"
-                    onClick={() => this.onPopUp()} className="button expand">
-                    <FontAwesomeIcon icon={faWindowRestore} />
-                </div>                          
+                    onClick={() => this.onPopUp()} className="button">
+                    <img src={PopUpIcon} alt=""/>
+                </div>
             </div>
         );
-
     }
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 0
nodeEditor/src/components/preview/svgs/colorPicker.svg


+ 1 - 0
nodeEditor/src/components/preview/svgs/depthPass.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>depthPass</title><g id="UI_Elements" data-name="UI Elements"><polygon class="cls-1" points="22.03 16.16 19.89 12.45 11.74 26.56 16.03 26.56 22.03 16.16"/><polygon class="cls-1" points="17.53 13.54 15.38 9.82 7.24 23.93 11.52 23.93 17.53 13.54"/><polygon class="cls-1" points="24.39 15.07 16.24 29.18 32.54 29.18 24.39 15.07"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/directionalLeft.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>directionalLeft</title><g id="UI_Elements" data-name="UI Elements"><polygon class="cls-1" points="27.89 22.51 27.89 28.12 21.52 21.75 25.61 17.66 11.27 10.81 10.01 12.07 16.86 26.41 20.74 22.53 27.11 28.9 21.5 28.9 21.5 30.01 29 30.01 29 22.51 27.89 22.51"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/directionalRight.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>directionalRight</title><g id="UI_Elements" data-name="UI Elements"><polygon class="cls-1" points="23.14 26.41 29.99 12.07 28.73 10.81 14.39 17.66 18.48 21.75 12.11 28.12 12.11 22.51 11 22.51 11 30.01 18.5 30.01 18.5 28.9 12.89 28.9 19.26 22.53 23.14 26.41"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/doubleSided.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>doubeSided</title><g id="UI_Elements" data-name="UI Elements"><polygon class="cls-1" points="27.3 14.7 26.52 15.48 30.48 19.44 21.33 19.44 21.33 11.15 18.65 11.15 18.65 19.44 9.52 19.44 13.48 15.48 12.7 14.7 7.39 20 12.7 25.3 13.48 24.52 9.52 20.56 18.65 20.56 18.65 28.85 21.33 28.85 21.33 20.56 30.48 20.56 26.52 24.52 27.3 25.3 32.61 20 27.3 14.7"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/omni.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>omni</title><g id="UI_Elements" data-name="UI Elements"><path class="cls-1" d="M26.22,20A6.22,6.22,0,1,1,20,13.78,6.23,6.23,0,0,1,26.22,20ZM11.51,17.6,9.11,20l2.4,2.4Zm.79,6.71V27.7h3.39Zm5.3,4.18,2.4,2.4,2.4-2.4Zm6.71-.79H27.7V24.31Zm4.18-5.3,2.4-2.4-2.4-2.4Zm-.79-6.71V12.3H24.31Zm-5.3-4.18L20,9.11l-2.4,2.4Zm-6.71.79H12.3v3.39Z"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/pauseIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>pauseIcon</title><g id="UI_Elements" data-name="UI Elements"><path class="cls-1" d="M17,28H13V12h4Zm9-16H22V28h4Z"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/playIcon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>playIcon</title><g id="UI_Elements" data-name="UI Elements"><polygon class="cls-1" points="12.99 9.98 12.99 28.04 29 19 12.99 9.98"/></g></svg>

+ 1 - 0
nodeEditor/src/components/preview/svgs/popOut.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><defs><style>.cls-1{fill:#fff;}</style></defs><title>popOut</title><g id="UI_Elements" data-name="UI Elements"><path class="cls-1" d="M10.5,10.5v19h19v-19Zm18,18H20V20H11.5V11.5h17Z"/></g></svg>

+ 3 - 3
nodeEditor/src/globalState.ts

@@ -63,9 +63,9 @@ export class GlobalState {
         this.directionalLight1 = DataStorage.ReadBoolean("DirectionalLight1", false);
         this.controlCamera = DataStorage.ReadBoolean("ControlCamera", true);
 
-        let r = DataStorage.ReadNumber("BackgroundColorR", 0.37);
-        let g = DataStorage.ReadNumber("BackgroundColorG", 0.37);
-        let b = DataStorage.ReadNumber("BackgroundColorB", 0.37);
+        let r = DataStorage.ReadNumber("BackgroundColorR", 32);
+        let g = DataStorage.ReadNumber("BackgroundColorG", 25);
+        let b = DataStorage.ReadNumber("BackgroundColorB", 64);
         this.backgroundColor = new Color4(r, g, b, 1.0);
     }
 }

+ 17 - 9
nodeEditor/src/graphEditor.tsx

@@ -638,8 +638,8 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
         parentControl.style.height = "100%";
         parentControl.style.margin = "0";
         parentControl.style.padding = "0";
-        parentControl.style.display = "block";
-        parentControl.style.gridTemplateRows = "unset";
+        parentControl.style.display = "grid";
+        parentControl.style.gridTemplateRows = "40px auto";
         parentControl.id = 'node-editor-graph-root';
         parentControl.className = 'right-panel';
 
@@ -671,9 +671,7 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
                     const newStyleEl = sourceDoc.createElement('style');
 
                     for (var cssRule of styleSheet.cssRules) {
-                        if (cssRule.selectorText !== '.right-panel #preview-config-bar .button') { // skip css grid layout rules
-                            newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
-                        }
+                        newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
                     }
 
                     targetDoc.head!.appendChild(newStyleEl);
@@ -688,12 +686,11 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
     createPreviewMeshControlHost = (options: IInternalPreviewAreaOptions, parentControl: Nullable<HTMLElement>) => {
         // Prepare the preview control host
         if (parentControl) {
+
             const host = parentControl.ownerDocument!.createElement("div");
 
             host.id = "PreviewMeshControl-host";
             host.style.width = options.embedHostWidth || "auto";
-            host.style.display = "block";
-            host.style.height = "30px";
 
             parentControl.appendChild(host);
             const PreviewMeshControlComponentHost = React.createElement(PreviewMeshControlComponent, {
@@ -711,7 +708,9 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
 
             host.id = "PreviewAreaComponent-host";
             host.style.width = options.embedHostWidth || "auto";
-            host.style.display = "block";
+            host.style.display = "grid";
+            host.style.gridRow = '2';
+            host.style.gridTemplateRows = "auto 40px";
 
             parentControl.appendChild(host);
 
@@ -734,12 +733,21 @@ export class GraphEditor extends React.Component<IGraphEditorProps, IGraphEditor
     fixPopUpStyles = (document: Document) => {
         const previewContainer = document.getElementById("preview");
         if (previewContainer) {
-            previewContainer.style.height = "calc(100% - 60px)";
+            previewContainer.style.height = "auto";
+            previewContainer.style.gridRow = "1";
+        }
+        const previewConfigBar = document.getElementById("preview-config-bar");
+        if (previewConfigBar) {
+            previewConfigBar.style.gridRow = "2";
         }
         const newWindowButton = document.getElementById('preview-new-window');
         if (newWindowButton) {
             newWindowButton.style.display = 'none';
         }
+        const previewMeshBar = document.getElementById('preview-mesh-bar');
+        if (previewMeshBar) {
+            previewMeshBar.style.gridTemplateColumns = "auto 1fr 40px 40px";
+        }
     }
 
     render() {

+ 77 - 18
nodeEditor/src/main.scss

@@ -61,7 +61,7 @@
     grid-row: 1 / span 2;
     grid-column: 5;
     display: grid;
-    grid-template-rows: 1fr 30px auto 30px;
+    grid-template-rows: 1fr 40px auto 40px;
     grid-template-columns: 100%;
     height: 100%;
     overflow-y: auto;
@@ -75,7 +75,7 @@
         display: grid;
         justify-content: center;
         align-content: center;
-        height: 30px;
+        height: auto;
         width: calc(100% / 7);
         cursor: pointer;
 
@@ -100,10 +100,10 @@
         grid-row: 2;
         grid-column: 1;
         display: grid;
-        grid-template-columns: auto 1fr 40px;
-        color: white;
+        grid-template-columns: auto 1fr 40px 40px 40px;
         align-items: center;
-        font-size: 18px;  
+        font-size: 18px;
+        background-color: #555555;
 
         #file-picker {
             display: none;
@@ -111,7 +111,7 @@
 
         .listLine {
             grid-column: 1;
-            height: 30px;
+            height: 40px;
             display: grid;
             grid-template-columns: 0px 1fr;  
     
@@ -135,33 +135,92 @@
             } 
         }
 
-        .expand {
+        .button{
+            color: #ffffff;
             width: 40px;
+            height: 40px;
+
+            &:hover {
+                background-color: #3f3461;
+            }
+
+            img{
+                height: 40px;
+                width: 100%;
+            }
+        }
+
+
+        #play-button {
             grid-column: 3;
         }
+
+        #color-picker-button {
+            grid-column: 4;
+
+            #color-picker {
+                display: none;
+            }
+
+            #color-picker-label {
+                width: 100%;
+                background: transparent;
+                cursor: pointer;            
+            }
+        }
+
+        #preview-new-window {
+            grid-column: 5;
+        }
+
+        select {
+            background-color: #a3a3a3;
+            color: #333333;
+        }
     }
 
     #preview-config-bar {
         grid-row: 4;
         grid-column: 1;
-        display: flex;
-        flex-direction: row-reverse;
+        display: grid;
+        grid-template-columns: 40px 40px 40px 1fr 40px 40px;
         color: white;
         align-items: center;
         font-size: 18px;    
 
         .button {
-            width: 60px;
-        }
+            width: 40px;
+            grid-row: 1;
+            height: 40px;
 
-        #color-picker {
-            display: none;
-        }
+            &.selected, &:hover {
+                background: #9379e6;
+            }
 
-        #color-picker-label {
-            width: 100%;
-            background: transparent;
-            cursor: pointer;            
+            img{
+                height: auto;
+                width: 100%;
+            }
+
+            &.back-face {
+                grid-column: 6
+            }
+
+            &.depth-pass {
+                grid-column: 5 / 6
+            }
+
+            &.hemispheric-light{
+                grid-column: 3 / 4
+            }
+            &.direction-light-1{
+                grid-column: 2 / 3
+
+            }
+            &.direction-light-0{
+                grid-column: 1 / 2
+                
+            }
         }
     }
     

+ 16 - 1
nodeEditor/webpack.config.js

@@ -20,7 +20,22 @@ var config = babylonWebpackConfig({
         {
             test: /\.css$/,
             use: ['style-loader', 'css-loader']
-        }],
+        },
+    //    {
+    //     test: /\.svg$/,
+    //     loader: 'svg-inline-loader'
+    // },
+    {
+        test: /\.svg$/,
+        use: [
+          {
+            loader: 'svg-url-loader',
+            options: {
+              limit: 10000,
+            },
+          },
+        ],
+      }],
     plugins: [
         new MiniCssExtractPlugin({
             // Options similar to the same options in webpackOptions.output

+ 2 - 1
package.json

@@ -92,6 +92,7 @@
         "sinon": "^6.1.4",
         "split.js": "^1.5.9",
         "style-loader": "^0.21.0",
+        "svg-url-loader": "^4.0.0",
         "through2": "~2.0.3",
         "ts-loader": "^5.2.1",
         "tslib": "^1.10.0",
@@ -106,4 +107,4 @@
         "xhr2": "^0.1.4",
         "xmlbuilder": "8.2.2"
     }
-}
+}