import * as React from "react" ; import { Project , File , Directory , FileType , ModelRef , isBinaryFileType , IStatusProvider } from "../models" ; import { Service } from "../service" ; import { ITree , ContextMenuEvent } from "../monaco-extra" ; import { MonacoUtils } from "../monaco-utils" ; import { ViewType } from "./editor/View" ; import { openFile , pushStatus , popStatus , logLn } from "../actions/AppActions" ; import { FileTemplate } from "../utils/Template" ; import { createController } from "../monaco-controller" ; import { DragAndDrop } from "../monaco-dnd" ; export interface DirectoryTreeProps { directory : ModelRef < Directory > ; value ? : ModelRef < File > ; onEditFile ? : ( file : File ) => void ; onDeleteFile ? : ( file : File ) => void ; onMoveFile ? : ( file : File , directory : Directory ) => void ; onNewFile ? : ( directory : Directory ) => void ; onNewDirectory ? : ( directory : Directory ) => void ; onClickFile ? : ( file : File ) => void ; onDoubleClickFile ? : ( file : File ) => void ; onUploadFile ? : ( directory : Directory ) => void ; onCreateGist ? : ( fileOrDirectory : File ) => void ; onlyUploadActions ? : boolean ; } export class DirectoryTree extends React . Component < DirectoryTreeProps , { directory : ModelRef < Directory > ; } > { status : IStatusProvider ; tree : ITree ; contextViewService : any ; contextMenuService : any ; container : HTMLDivElement ; lastClickedTime = Date . now ( ) ; lastClickedFile : File | null = null ; constructor ( props ) { super ( props ) ; this . contextViewService = new MonacoUtils . ContextViewService ( document . documentElement , null , { trace : ( ) => { } } ) ; this . contextMenuService = new MonacoUtils . ContextMenuService ( document . documentElement , null , null , this . contextViewService ) ; this . state = { directory : this . props . directory } ; this . status = { push : pushStatus , pop : popStatus , logLn : logLn } ; } componentDidMount ( ) { this . ensureTree ( ) ; ( this . tree as any ) . model . setInput ( this . props . directory . getModel ( ) ) ; ( this . tree as any ) . model . onDidSelect ( ( e ) => { if ( e . selection . length ) { this . onClickFile ( e . selection [ 0 ] ) ; } } ) ; document . addEventListener ( "layout" , this . onLayout ) ; } componentWillUnmount ( ) { document . removeEventListener ( "layout" , this . onLayout ) ; } componentWillReceiveProps ( props ) { if ( this . state . directory !== props . directory ) { ( this . tree as any ) . model . setInput ( props . directory . getModel ( ) ) ; this . setState ( { directory : props . directory } ) ; } else { this . tree . refresh ( ) ; MonacoUtils . expandTree ( this . tree ) ; } } private setContainer ( container ) { if ( container == null ) { return ; } this . container = container ; } private ensureTree ( ) { if ( this . container . lastChild ) { this . container . removeChild ( this . container . lastChild ) ; } this . tree = new MonacoUtils . Tree ( this . container , { dataSource : { /**
                 * Returns the unique identifier of the given element.
                 * No more than one element may use a given identifier.
                 */ getId : function ( tree , element ) : string { return element . key ; } , /**
                 * Returns a boolean value indicating whether the element has children.
                 */ hasChildren : function ( tree , element ) : boolean { return element instanceof Directory ; } , /**
                 * Returns the element's children as an array in a promise.
                 */ getChildren : function ( tree , element ) : monaco . Promise < any > { return monaco . Promise . as ( element . children ) ; } , /**
                 * Returns the element's parent in a promise.
                 */ getParent : function ( tree , element ) : monaco . Promise < any > { return monaco . Promise . as ( element . parent ) ; } } , renderer : { getHeight : function ( tree , element ) : number { return 24 ; } , renderTemplate : function ( tree , templateId , container ) : any { return new FileTemplate ( container ) ; } , renderElement : function ( tree , element , templateId , templateData ) : void { ( templateData as FileTemplate ) . set ( element ) ; } , disposeTemplate : function ( tree , templateId , templateData ) : void { ( templateData as FileTemplate ) . dispose ( ) ; } } , controller : createController ( this , ( file , event ) => this . getActions ( file , event ) , true ) , dnd : new DragAndDrop ( this ) } ) ; } getActions ( file , event ) { const actions = [ ] ; if ( this . props . onlyUploadActions ) { if ( ! file . parent ) { return actions ; } this . props . onDeleteFile && actions . push ( new MonacoUtils . Action ( "x" , "Delete" , "octicon-x" , true , ( ) => { return this . props . onDeleteFile ( file as Directory ) ; } ) ) ; this . props . onEditFile && actions . push ( new MonacoUtils . Action ( "x" , "Edit" , "octicon-pencil" , true , ( ) => { return this . props . onEditFile ( file as Directory ) ; } ) ) ; return actions ; } if ( file instanceof Directory ) { this . props . onNewFile && actions . push ( new MonacoUtils . Action ( "x" , "New File" , "octicon-file-add" , true , ( ) => { return this . props . onNewFile ( file as Directory ) ; } ) ) ; this . props . onNewDirectory && actions . push ( new MonacoUtils . Action ( "x" , "New Directory" , "octicon-file-add" , true , ( ) => { return this . props . onNewDirectory ( file as Directory ) ; } ) ) ; this . props . onUploadFile && actions . push ( new MonacoUtils . Action ( "x" , "Upload Files" , "octicon-cloud-upload" , true , ( ) => { return this . props . onUploadFile ( file as Directory ) ; } ) ) ; } if ( ! ( file instanceof Project ) ) { this . props . onEditFile && actions . push ( new MonacoUtils . Action ( "x" , "Edit" , "octicon-pencil" , true , ( ) => { return this . props . onEditFile ( file as Directory ) ; } ) ) ; this . props . onDeleteFile && actions . push ( new MonacoUtils . Action ( "x" , "Delete" , "octicon-x" , true , ( ) => { return this . props . onDeleteFile ( file as Directory ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Download" , "octicon-cloud-download" , true , ( ) => { Service . download ( file ) ; } ) ) ; } if ( ! isBinaryFileType ( file . type ) ) { this . props . onCreateGist && actions . push ( new MonacoUtils . Action ( "x" , "Create Gist" , "octicon-gist" , true , ( ) => { return this . props . onCreateGist ( file as Directory ) ; } ) ) ; } if ( file . type === FileType . Wasm ) { actions . push ( new MonacoUtils . Action ( "x" , "Validate" , "octicon-check ruler" , true , async ( ) => { const result = await Service . validateWasmWithBinaryen ( file , this . status ) ; window . alert ( result ? "Module is valid" : "Module is not valid" ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Optimize" , "octicon-gear" , true , ( ) => { Service . optimizeWasmWithBinaryen ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Disassemble" , "octicon-file-code" , true , ( ) => { Service . disassembleWasmWithWabt ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Disassemble w/ Binaryen" , "octicon-file-code" , true , ( ) => { Service . disassembleWasmWithBinaryen ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "To asm.js" , "octicon-file-code" , true , ( ) => { Service . convertWasmToAsmWithBinaryen ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Generate Call Graph" , "octicon-gear" , true , ( ) => { Service . getWasmCallGraphWithBinaryen ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "To Firefox x86" , "octicon-file-binary" , true , ( ) => { Service . disassembleX86 ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "To Firefox x86 Baseline" , "octicon-file-binary" , true , ( ) => { Service . disassembleX86 ( file , this . status , "--wasm-always-baseline" ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Binary Explorer" , "octicon-file-binary" , true , ( ) => { Service . openBinaryExplorer ( file ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "View as Binary" , "octicon-file-binary" , true , ( ) => { openFile ( file , ViewType . Binary , false ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Twiggy" , "octicon-file-binary" , true , ( ) => { Service . twiggyWasm ( file , this . status ) ; } ) ) ; } else if ( file . type === FileType . C || file . type === FileType . Cpp ) { actions . push ( new MonacoUtils . Action ( "x" , "Clang-Format" , "octicon-quote ruler" , true , ( ) => { Service . clangFormat ( file , this . status ) ; } ) ) ; } else if ( file . type === FileType . Wat ) { actions . push ( new MonacoUtils . Action ( "x" , "Assemble" , "octicon-file-binary ruler" , true , ( ) => { Service . assembleWatWithWabt ( file , this . status ) ; } ) ) ; actions . push ( new MonacoUtils . Action ( "x" , "Assemble w/ Binaryen" , "octicon-file-binary" , true , ( ) => { Service . assembleWatWithBinaryen ( file , this . status ) ; } ) ) ; } return actions ; } onClickFile ( file ) { if ( file instanceof Directory ) { return ; } if ( Date . now ( ) - this . lastClickedTime < 500 && this . lastClickedFile === file && this . props . onDoubleClickFile ) { this . props . onDoubleClickFile ( file ) ; } else if ( this . props . onClickFile ) { this . props . onClickFile ( file ) ; } this . lastClickedTime = Date . now ( ) ; this . lastClickedFile = file ; } onLayout = ( ) => { this . tree . layout ( ) ; } ; render ( ) { return < div className = "fill" ref = { ( ref ) => this . setContainer ( ref ) } />; } }