Skip to content

⬅️ Back to Table of Contents

📄 Menubar.File.js

📊 Analysis Summary

Metric Count
🔧 Functions 3
📦 Imports 3
📊 Variables & Constants 29
⚡ Async/Await Patterns 2

📚 Table of Contents

🛠️ File Location:

📂 editor/js/Menubar.File.js

📦 Imports

Name Source
UIPanel ./libs/ui.js
UIRow ./libs/ui.js
UIHorizontalRule ./libs/ui.js

Variables & Constants

Name Type Kind Value Exported
strings any let/var editor.strings
saveArrayBuffer any let/var editor.utils.saveArrayBuffer
saveString any let/var editor.utils.saveString
container UIPanel let/var new UIPanel()
title UIPanel let/var new UIPanel()
options UIPanel let/var new UIPanel()
examples { title: string; file: string; }[] let/var [ { title: 'menubar/file/new/Arkanoid', file: 'arkanoid.app.json' }, { title:...
loader any let/var new THREE.FileLoader()
example { title: string; file: string; } let/var examples[ i ]
option UIRow let/var new UIRow()
file File let/var openProjectInput.files[ 0 ]
blob Blob let/var new Blob( [ JSON.stringify( json ) ], { type: 'application/json' } )
object any let/var editor.selected
exporter any let/var new DRACOExporter()
options { decodeSpeed: number; encodeSpeed: n... let/var { decodeSpeed: 5, encodeSpeed: 5, encoderMethod: DRACOExporter.MESH_EDGEBREAK...
scene any let/var editor.scene
optimizedAnimations any[] let/var []
exporter any let/var new GLTFExporter()
scene any let/var editor.scene
optimizedAnimations any[] let/var []
exporter any let/var new GLTFExporter()
object any let/var editor.selected
exporter any let/var new OBJExporter()
exporter any let/var new PLYExporter()
exporter any let/var new PLYExporter()
exporter any let/var new STLExporter()
exporter any let/var new STLExporter()
exporter any let/var new USDZExporter()
animations any[] let/var []

Async/Await Patterns

Type Function Await Expressions Promise Chains
await-expression MenubarFile file.text(), editor.fromJSON( json ), import( 'three/addons/exporters/DRACOExporter.js' ), import( 'three/addons/exporters/GLTFExporter.js' ), import( 'three/addons/exporters/GLTFExporter.js' ), import( 'three/addons/exporters/OBJExporter.js' ), import( 'three/addons/exporters/PLYExporter.js' ), import( 'three/addons/exporters/PLYExporter.js' ), import( 'three/addons/exporters/STLExporter.js' ), import( 'three/addons/exporters/STLExporter.js' ), import( 'three/addons/exporters/USDZExporter.js' ), exporter.parseAsync( editor.scene ) none
async-function onEditorCleared editor.fromJSON( json ) none

Functions

Parameters:

  • editor any

Returns: UIPanel

Calls:

  • container.setClass
  • title.setClass
  • title.setTextContent
  • strings.getKey
  • container.add
  • options.setClass
  • new UIRow().setTextContent( strings.getKey( 'menubar/file/new' ) ).addClass( 'option' ).addClass
  • newProjectSubmenuTitle.onMouseOver
  • this.dom.getBoundingClientRect
  • getComputedStyle
  • newProjectSubmenu.setLeft
  • newProjectSubmenu.setTop
  • parseFloat
  • newProjectSubmenu.setDisplay
  • newProjectSubmenuTitle.onMouseOut
  • options.add
  • new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay
  • newProjectSubmenuTitle.add
  • new UIRow().setTextContent( strings.getKey( 'menubar/file/new/empty' ) ).setClass
  • option.onClick
  • confirm
  • editor.clear
  • newProjectSubmenu.add
  • complex_call_2152
  • option.setClass
  • option.setTextContent
  • loader.load
  • editor.fromJSON
  • JSON.parse
  • document.createElement
  • document.body.appendChild
  • openProjectInput.addEventListener
  • file.text
  • editor.signals.editorCleared.remove
  • editor.signals.editorCleared.add
  • alert
  • console.error
  • form.reset
  • openProjectForm.appendChild
  • new UIRow() .addClass( 'option' ) .setTextContent( strings.getKey( 'menubar/file/open' ) ) .onClick
  • openProjectInput.click
  • new UIRow() .addClass( 'option' ) .setTextContent( strings.getKey( 'menubar/file/save' ) ) .onClick
  • editor.toJSON
  • JSON.stringify
  • editor.utils.save
  • fileInput.addEventListener
  • editor.loader.loadFiles
  • form.appendChild
  • fileInput.click
  • new UIRow().setTextContent( strings.getKey( 'menubar/file/export' ) ).addClass( 'option' ).addClass
  • fileExportSubmenuTitle.onMouseOver
  • fileExportSubmenu.setLeft
  • fileExportSubmenu.setTop
  • fileExportSubmenu.setDisplay
  • fileExportSubmenuTitle.onMouseOut
  • fileExportSubmenuTitle.add
  • complex_call_5989
  • object.geometry.hasAttribute
  • exporter.parse
  • saveArrayBuffer
  • fileExportSubmenu.add
  • getAnimations
  • optimizedAnimations.push
  • animation.clone().optimize
  • complex_call_6939
  • complex_call_7631
  • saveString
  • complex_call_8258
  • complex_call_8626
  • complex_call_9055
  • complex_call_9499
  • complex_call_9883
  • complex_call_10282
  • exporter.parseAsync
  • scene.traverse
  • animations.push

Internal Comments:

// New Project (x2)
// New Project / Empty (x2)
// (x9)
// New Project / ... (x2)
// Open (x2)
// Save (x3)
// Import (x2)
// Export (x2)
// Export DRC (x3)
// TODO: Change to DRACOExporter's parse( geometry, onParse )? (x2)
// Export GLB (x3)
// Export GLTF (x3)
// Export OBJ (x3)
// Export PLY (ASCII) (x3)
// Export PLY (BINARY) (x3)
// Export STL (ASCII) (x3)
// Export STL (BINARY) (x3)
// Export USDZ (x3)

Code
function MenubarFile( editor ) {

    const strings = editor.strings;

    const saveArrayBuffer = editor.utils.saveArrayBuffer;
    const saveString = editor.utils.saveString;

    const container = new UIPanel();
    container.setClass( 'menu' );

    const title = new UIPanel();
    title.setClass( 'title' );
    title.setTextContent( strings.getKey( 'menubar/file' ) );
    container.add( title );

    const options = new UIPanel();
    options.setClass( 'options' );
    container.add( options );

    // New Project

    const newProjectSubmenuTitle = new UIRow().setTextContent( strings.getKey( 'menubar/file/new' ) ).addClass( 'option' ).addClass( 'submenu-title' );
    newProjectSubmenuTitle.onMouseOver( function () {

        const { top, right } = this.dom.getBoundingClientRect();
        const { paddingTop } = getComputedStyle( this.dom );
        newProjectSubmenu.setLeft( right + 'px' );
        newProjectSubmenu.setTop( top - parseFloat( paddingTop ) + 'px' );
        newProjectSubmenu.setDisplay( 'block' );

    } );
    newProjectSubmenuTitle.onMouseOut( function () {

        newProjectSubmenu.setDisplay( 'none' );

    } );
    options.add( newProjectSubmenuTitle );

    const newProjectSubmenu = new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay( 'none' );
    newProjectSubmenuTitle.add( newProjectSubmenu );

    // New Project / Empty

    let option = new UIRow().setTextContent( strings.getKey( 'menubar/file/new/empty' ) ).setClass( 'option' );
    option.onClick( function () {

        if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {

            editor.clear();

        }

    } );
    newProjectSubmenu.add( option );

    //

    newProjectSubmenu.add( new UIHorizontalRule() );

    // New Project / ...

    const examples = [
        { title: 'menubar/file/new/Arkanoid', file: 'arkanoid.app.json' },
        { title: 'menubar/file/new/Camera', file: 'camera.app.json' },
        { title: 'menubar/file/new/Particles', file: 'particles.app.json' },
        { title: 'menubar/file/new/Pong', file: 'pong.app.json' },
        { title: 'menubar/file/new/Shaders', file: 'shaders.app.json' }
    ];

    const loader = new THREE.FileLoader();

    for ( let i = 0; i < examples.length; i ++ ) {

        ( function ( i ) {

            const example = examples[ i ];

            const option = new UIRow();
            option.setClass( 'option' );
            option.setTextContent( strings.getKey( example.title ) );
            option.onClick( function () {

                if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {

                    loader.load( 'examples/' + example.file, function ( text ) {

                        editor.clear();
                        editor.fromJSON( JSON.parse( text ) );

                    } );

                }

            } );
            newProjectSubmenu.add( option );

        } )( i );

    }

    // Open

    const openProjectForm = document.createElement( 'form' );
    openProjectForm.style.display = 'none';
    document.body.appendChild( openProjectForm );

    const openProjectInput = document.createElement( 'input' );
    openProjectInput.multiple = false;
    openProjectInput.type = 'file';
    openProjectInput.accept = '.json';
    openProjectInput.addEventListener( 'change', async function () {

        const file = openProjectInput.files[ 0 ];

        if ( file === undefined ) return;

        try {

            const json = JSON.parse( await file.text() );

            async function onEditorCleared() {

                await editor.fromJSON( json );

                editor.signals.editorCleared.remove( onEditorCleared );

            }

            editor.signals.editorCleared.add( onEditorCleared );

            editor.clear();

        } catch ( e ) {

            alert( strings.getKey( 'prompt/file/failedToOpenProject' ) );
            console.error( e );

        } finally {

            form.reset();

        }

    } );

    openProjectForm.appendChild( openProjectInput );

    option = new UIRow()
        .addClass( 'option' )
        .setTextContent( strings.getKey( 'menubar/file/open' ) )
        .onClick( function () {

            if ( confirm( strings.getKey( 'prompt/file/open' ) ) ) {

                openProjectInput.click();

            }

        } );

    options.add( option );

    // Save

    option = new UIRow()
        .addClass( 'option' )
        .setTextContent( strings.getKey( 'menubar/file/save' ) )
        .onClick( function () {

            const json = editor.toJSON();
            const blob = new Blob( [ JSON.stringify( json ) ], { type: 'application/json' } );
            editor.utils.save( blob, 'project.json' );

        } );

    options.add( option );

    //

    options.add( new UIHorizontalRule() );

    // Import

    const form = document.createElement( 'form' );
    form.style.display = 'none';
    document.body.appendChild( form );

    const fileInput = document.createElement( 'input' );
    fileInput.multiple = true;
    fileInput.type = 'file';
    fileInput.addEventListener( 'change', function () {

        editor.loader.loadFiles( fileInput.files );
        form.reset();

    } );
    form.appendChild( fileInput );

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( strings.getKey( 'menubar/file/import' ) );
    option.onClick( function () {

        fileInput.click();

    } );
    options.add( option );

    // Export

    const fileExportSubmenuTitle = new UIRow().setTextContent( strings.getKey( 'menubar/file/export' ) ).addClass( 'option' ).addClass( 'submenu-title' );
    fileExportSubmenuTitle.onMouseOver( function () {

        const { top, right } = this.dom.getBoundingClientRect();
        const { paddingTop } = getComputedStyle( this.dom );
        fileExportSubmenu.setLeft( right + 'px' );
        fileExportSubmenu.setTop( top - parseFloat( paddingTop ) + 'px' );
        fileExportSubmenu.setDisplay( 'block' );

    } );
    fileExportSubmenuTitle.onMouseOut( function () {

        fileExportSubmenu.setDisplay( 'none' );

    } );
    options.add( fileExportSubmenuTitle );

    const fileExportSubmenu = new UIPanel().setPosition( 'fixed' ).addClass( 'options' ).setDisplay( 'none' );
    fileExportSubmenuTitle.add( fileExportSubmenu );

    // Export DRC

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'DRC' );
    option.onClick( async function () {

        const object = editor.selected;

        if ( object === null || object.isMesh === undefined ) {

            alert( strings.getKey( 'prompt/file/export/noMeshSelected' ) );
            return;

        }

        const { DRACOExporter } = await import( 'three/addons/exporters/DRACOExporter.js' );

        const exporter = new DRACOExporter();

        const options = {
            decodeSpeed: 5,
            encodeSpeed: 5,
            encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
            quantization: [ 16, 8, 8, 8, 8 ],
            exportUvs: true,
            exportNormals: true,
            exportColor: object.geometry.hasAttribute( 'color' )
        };

        // TODO: Change to DRACOExporter's parse( geometry, onParse )?
        const result = exporter.parse( object, options );
        saveArrayBuffer( result, 'model.drc' );

    } );
    fileExportSubmenu.add( option );

    // Export GLB

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'GLB' );
    option.onClick( async function () {

        const scene = editor.scene;
        const animations = getAnimations( scene );

        const optimizedAnimations = [];

        for ( const animation of animations ) {

            optimizedAnimations.push( animation.clone().optimize() );

        }

        const { GLTFExporter } = await import( 'three/addons/exporters/GLTFExporter.js' );

        const exporter = new GLTFExporter();

        exporter.parse( scene, function ( result ) {

            saveArrayBuffer( result, 'scene.glb' );

        }, undefined, { binary: true, animations: optimizedAnimations } );

    } );
    fileExportSubmenu.add( option );

    // Export GLTF

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'GLTF' );
    option.onClick( async function () {

        const scene = editor.scene;
        const animations = getAnimations( scene );

        const optimizedAnimations = [];

        for ( const animation of animations ) {

            optimizedAnimations.push( animation.clone().optimize() );

        }

        const { GLTFExporter } = await import( 'three/addons/exporters/GLTFExporter.js' );

        const exporter = new GLTFExporter();

        exporter.parse( scene, function ( result ) {

            saveString( JSON.stringify( result, null, 2 ), 'scene.gltf' );

        }, undefined, { animations: optimizedAnimations } );


    } );
    fileExportSubmenu.add( option );

    // Export OBJ

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'OBJ' );
    option.onClick( async function () {

        const object = editor.selected;

        if ( object === null ) {

            alert( strings.getKey( 'prompt/file/export/noObjectSelected' ) );
            return;

        }

        const { OBJExporter } = await import( 'three/addons/exporters/OBJExporter.js' );

        const exporter = new OBJExporter();

        saveString( exporter.parse( object ), 'model.obj' );

    } );
    fileExportSubmenu.add( option );

    // Export PLY (ASCII)

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'PLY' );
    option.onClick( async function () {

        const { PLYExporter } = await import( 'three/addons/exporters/PLYExporter.js' );

        const exporter = new PLYExporter();

        exporter.parse( editor.scene, function ( result ) {

            saveArrayBuffer( result, 'model.ply' );

        } );

    } );
    fileExportSubmenu.add( option );

    // Export PLY (BINARY)

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'PLY (BINARY)' );
    option.onClick( async function () {

        const { PLYExporter } = await import( 'three/addons/exporters/PLYExporter.js' );

        const exporter = new PLYExporter();

        exporter.parse( editor.scene, function ( result ) {

            saveArrayBuffer( result, 'model-binary.ply' );

        }, { binary: true } );

    } );
    fileExportSubmenu.add( option );

    // Export STL (ASCII)

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'STL' );
    option.onClick( async function () {

        const { STLExporter } = await import( 'three/addons/exporters/STLExporter.js' );

        const exporter = new STLExporter();

        saveString( exporter.parse( editor.scene ), 'model.stl' );

    } );
    fileExportSubmenu.add( option );

    // Export STL (BINARY)

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'STL (BINARY)' );
    option.onClick( async function () {

        const { STLExporter } = await import( 'three/addons/exporters/STLExporter.js' );

        const exporter = new STLExporter();

        saveArrayBuffer( exporter.parse( editor.scene, { binary: true } ), 'model-binary.stl' );

    } );
    fileExportSubmenu.add( option );

    // Export USDZ

    option = new UIRow();
    option.setClass( 'option' );
    option.setTextContent( 'USDZ' );
    option.onClick( async function () {

        const { USDZExporter } = await import( 'three/addons/exporters/USDZExporter.js' );

        const exporter = new USDZExporter();

        saveArrayBuffer( await exporter.parseAsync( editor.scene ), 'model.usdz' );

    } );
    fileExportSubmenu.add( option );

    //

    function getAnimations( scene ) {

        const animations = [];

        scene.traverse( function ( object ) {

            animations.push( ... object.animations );

        } );

        return animations;

    }

    return container;

}

onEditorCleared(): Promise<void>

Returns: Promise<void>

Calls:

  • editor.fromJSON
  • editor.signals.editorCleared.remove
Code
async function onEditorCleared() {

                await editor.fromJSON( json );

                editor.signals.editorCleared.remove( onEditorCleared );

            }

getAnimations(scene: any): any[]

Parameters:

  • scene any

Returns: any[]

Calls:

  • scene.traverse
  • animations.push
Code
function getAnimations( scene ) {

        const animations = [];

        scene.traverse( function ( object ) {

            animations.push( ... object.animations );

        } );

        return animations;

    }