@@ -125,23 +125,26 @@ const createInitialPaths = (core, proc) => {
125125 return { homePath, initialPath} ;
126126} ;
127127
128- /**
129- * Formats file status message
130- */
131- const formatFileMessage = file => `${ file . filename } (${ file . size } bytes)` ;
128+ const getDirectoryCount = files =>
129+ files . filter ( file => file . isDirectory ) . length ;
130+ const getFileCount = files =>
131+ files . filter ( file => ! file . isDirectory ) . length ;
132+ const getTotalSize = files =>
133+ files . reduce ( ( total , file ) => total + ( file . size || 0 ) , 0 ) ;
132134
133135/**
134136 * Formats directory status message
135137 */
136- const formatStatusMessage = ( core ) => {
138+ const formatStatusMessage = ( core , files ) => {
137139 const { translatable} = core . make ( 'osjs/locale' ) ;
138140 const __ = translatable ( translations ) ;
139141
140142 return ( path , files ) => {
141- const directoryCount = files . filter ( f => f . isDirectory ) . length ;
142- const fileCount = files . filter ( f => ! f . isDirectory ) . length ;
143- const totalSize = files . reduce ( ( t , f ) => t + ( f . size || 0 ) , 0 ) ;
143+ const directoryCount = getDirectoryCount ( files ) ;
144+ const fileCount = getFileCount ( files ) ;
145+ const totalSize = getTotalSize ( files ) ;
144146
147+ // TODO: Copy over new label messages with translation
145148 return __ ( 'LBL_STATUS' , directoryCount , fileCount , totalSize ) ;
146149 } ;
147150} ;
@@ -318,6 +321,8 @@ const vfsActionFactory = (core, proc, win, dialog, state) => {
318321 const readdir = async ( dir , history , selectFile ) => {
319322 if ( win . getState ( 'loading' ) ) {
320323 return ;
324+ } else if ( Array . isArray ( dir ) ) {
325+ dir = dir [ 0 ] ;
321326 }
322327
323328 try {
@@ -347,7 +352,7 @@ const vfsActionFactory = (core, proc, win, dialog, state) => {
347352 } catch ( error ) {
348353 dialog ( 'error' , error , __ ( 'MSG_READDIR_ERROR' , dir . path ) ) ;
349354 } finally {
350- state . currentFile = undefined ;
355+ state . currentFile = [ ] ;
351356 win . setState ( 'loading' , false ) ;
352357 }
353358 } ;
@@ -359,25 +364,32 @@ const vfsActionFactory = (core, proc, win, dialog, state) => {
359364 } ) ;
360365
361366 const paste = ( move , currentPath ) => ( { item, callback} ) => {
362- const dest = { path : pathJoin ( currentPath . path , item . filename ) } ;
367+ const promises = items . map ( item => {
368+ const dest = {
369+ path : pathJoin ( currentPath . path , item . filename )
370+ } ;
363371
364- const fn = move
365- ? vfs . move ( item , dest , { pid : proc . pid } )
366- : vfs . copy ( item , dest , { pid : proc . pid } ) ;
372+ return move
373+ ? vfs . move ( item , dest , { pid : proc . pid } )
374+ : vfs . copy ( item , dest , { pid : proc . pid } ) ;
375+ } ) ;
367376
368- return fn
369- . then ( ( ) => {
377+ return Promise
378+ . all ( promises )
379+ . then ( results => {
370380 refresh ( true ) ;
371381
372382 if ( typeof callback === 'function' ) {
373383 callback ( ) ;
374384 }
385+
386+ return results ;
375387 } )
376388 . catch ( error => dialog ( 'error' , error , __ ( 'MSG_PASTE_ERROR' ) ) ) ;
377389 } ;
378390
379391 return {
380- download : file => vfs . download ( file ) ,
392+ download : files => files . forEach ( file => vfs . download ( file ) ) ,
381393 upload,
382394 refresh,
383395 action,
@@ -430,23 +442,34 @@ const dialogFactory = (core, proc, win) => {
430442 value : __ ( 'DIALOG_MKDIR_PLACEHOLDER' )
431443 } , usingPositiveButton ( value => {
432444 const newPath = pathJoin ( currentPath . path , value ) ;
433- action ( ( ) => vfs . mkdir ( { path : newPath } , { pid : proc . pid } ) , value , __ ( 'MSG_MKDIR_ERROR' ) ) ;
445+ action (
446+ ( ) => vfs . mkdir ( { path : newPath } , { pid : proc . pid } ) ,
447+ value ,
448+ __ ( 'MSG_MKDIR_ERROR' )
449+ ) ;
434450 } ) ) ;
435451
436- const renameDialog = ( action , file ) => dialog ( 'prompt' , {
437- message : __ ( 'DIALOG_RENAME_MESSAGE' , file . filename ) ,
438- value : file . filename
439- } , usingPositiveButton ( value => {
440- const idx = file . path . lastIndexOf ( file . filename ) ;
441- const newPath = file . path . substr ( 0 , idx ) + value ;
452+ const renameDialog = ( action , files ) => files . forEach ( file =>
453+ dialog ( 'prompt' , {
454+ message : __ ( 'DIALOG_RENAME_MESSAGE' , file . filename ) ,
455+ value : file . filename
456+ } , usingPositiveButton ( value => {
457+ const idx = file . path . lastIndexOf ( file . filename ) ;
458+ const newPath = file . path . substr ( 0 , idx ) + value ;
442459
443- action ( ( ) => vfs . rename ( file , { path : newPath } ) , value , __ ( 'MSG_RENAME_ERROR' ) ) ;
444- } ) ) ;
460+ action ( ( ) => vfs . rename ( file , { path : newPath } ) , value , __ ( 'MSG_RENAME_ERROR' ) ) ;
461+ } ) ) ) ;
445462
446- const deleteDialog = ( action , file ) => dialog ( 'confirm' , {
463+ const deleteDialog = ( action , files ) => dialog ( 'confirm' , {
447464 message : __ ( 'DIALOG_DELETE_MESSAGE' , file . filename ) ,
448465 } , usingPositiveButton ( ( ) => {
449- action ( ( ) => vfs . unlink ( file , { pid : proc . pid } ) , true , __ ( 'MSG_DELETE_ERROR' ) ) ;
466+ action (
467+ ( ) => Promise . all (
468+ files . map ( file => vfs . unlink ( file , { pid : proc . pid } ) )
469+ ) ,
470+ true ,
471+ __ ( 'MSG_DELETE_ERROR' )
472+ ) ;
450473 } ) ) ;
451474
452475 const progressDialog = ( file ) => dialog ( 'progress' , {
@@ -512,40 +535,44 @@ const menuFactory = (core, proc, win) => {
512535 { label : _ ( 'LBL_QUIT' ) , onclick : ( ) => win . emit ( 'filemanager:menu:quit' ) }
513536 ] ) ;
514537
515- const createEditMenu = async ( item , isContextMenu ) => {
516- const emitter = name => win . emit ( name , item ) ;
538+ const createEditMenu = async ( items , isContextMenu ) => {
539+ const emitter = name => win . emit ( name , items ) ;
540+ const item = items [ items . length - 1 ] ;
517541
518- if ( item && isSpecialFile ( item . filename ) ) {
519- return [ {
542+ if ( items . length === 1 && item && isSpecialFile ( item . filename ) ) {
543+ return [ {
520544 label : _ ( 'LBL_GO' ) ,
521- onclick : ( ) => emitter ( 'filemanager:navigate' )
522- } ] ;
523- }
545+ onclick : ( ) => emitter ( 'filemanager:navigate' ) ,
546+ } ] ;
547+ }
524548
525- const isValidFile = item && ! isSpecialFile ( item . filename ) ;
526- const isDirectory = item && item . isDirectory ;
549+ const canDownload = items . some (
550+ item => ! item . isDirectory && ! isSpecialFile ( item . filename )
551+ ) ;
552+ const hasValidFile = items . some ( item => ! isSpecialFile ( item . filename ) ) ;
553+ const isDirectory = items . length === 1 && item . isDirectory ;
527554
528555 const openMenu = isDirectory ? [ {
529556 label : _ ( 'LBL_GO' ) ,
530- disabled : ! item ,
557+ disabled : ! items . length ,
531558 onclick : ( ) => emitter ( 'filemanager:navigate' )
532559 } ] : [ {
533560 label : _ ( 'LBL_OPEN' ) ,
534- disabled : ! item ,
561+ disabled : ! items . length ,
535562 onclick : ( ) => emitter ( 'filemanager:open' )
536563 } , {
537564 label : __ ( 'LBL_OPEN_WITH' ) ,
538- disabled : ! item ,
565+ disabled : ! items . length ,
539566 onclick : ( ) => emitter ( 'filemanager:openWith' )
540567 } ] ;
541568
542569 const clipboardMenu = [ {
543570 label : _ ( 'LBL_COPY' ) ,
544- disabled : ! isValidFile ,
571+ disabled : ! hasValidFile ,
545572 onclick : ( ) => emitter ( 'filemanager:menu:copy' )
546573 } , {
547574 label : _ ( 'LBL_CUT' ) ,
548- disabled : ! isValidFile ,
575+ disabled : ! hasValidFile ,
549576 onclick : ( ) => emitter ( 'filemanager:menu:cut' )
550577 } ] ;
551578
@@ -563,7 +590,7 @@ const menuFactory = (core, proc, win) => {
563590 if ( core . config ( 'filemanager.disableDownload' , false ) !== true ) {
564591 configuredItems . push ( {
565592 label : _ ( 'LBL_DOWNLOAD' ) ,
566- disabled : ! item || isDirectory || ! isValidFile ,
593+ disabled : ! canDownload ,
567594 onclick : ( ) => emitter ( 'filemanager:menu:download' )
568595 } ) ;
569596 }
@@ -572,12 +599,12 @@ const menuFactory = (core, proc, win) => {
572599 ...openMenu ,
573600 {
574601 label : _ ( 'LBL_RENAME' ) ,
575- disabled : ! isValidFile ,
602+ disabled : ! hasValidFile ,
576603 onclick : ( ) => emitter ( 'filemanager:menu:rename' )
577604 } ,
578605 {
579606 label : _ ( 'LBL_DELETE' ) ,
580- disabled : ! isValidFile ,
607+ disabled : ! hasValidFile ,
581608 onclick : ( ) => emitter ( 'filemanager:menu:delete' )
582609 } ,
583610 ...clipboardMenu ,
@@ -685,7 +712,6 @@ const createApplication = (core, proc) => {
685712 const createRows = listViewRowFactory ( core , proc ) ;
686713 const createMounts = mountViewRowsFactory ( core ) ;
687714 const { draggable} = core . make ( 'osjs/dnd' ) ;
688- const statusMessage = formatStatusMessage ( core ) ;
689715
690716 const initialState = {
691717 path : '' ,
@@ -705,7 +731,9 @@ const createApplication = (core, proc) => {
705731 } ) ,
706732
707733 fileview : listView . state ( {
708- columns : [ ]
734+ columns : [ ] ,
735+ multiselect : true ,
736+ previousSelectedIndex : 0
709737 } )
710738 } ;
711739
@@ -742,18 +770,18 @@ const createApplication = (core, proc) => {
742770 setStatus : status => ( { status} ) ,
743771 setMinimalistic : minimalistic => ( { minimalistic} ) ,
744772 setList : ( { list, path, selectFile} ) => ( { fileview, mountview} ) => {
745- let selectedIndex ;
773+ let selectedIndex = [ ] ;
746774
747775 if ( selectFile ) {
748776 const foundIndex = list . findIndex ( file => file . filename === selectFile ) ;
749777 if ( foundIndex !== - 1 ) {
750- selectedIndex = foundIndex ;
778+ selectedIndex = [ foundIndex ] ;
751779 }
752780 }
753781
754782 return {
755783 path,
756- status : statusMessage ( path , list ) ,
784+ status : formatStatusMessage ( list ) ,
757785 mountview : Object . assign ( { } , mountview , {
758786 rows : createMounts ( )
759787 } ) ,
@@ -771,7 +799,10 @@ const createApplication = (core, proc) => {
771799
772800 fileview : listView . actions ( {
773801 select : ( { data} ) => win . emit ( 'filemanager:select' , data ) ,
774- activate : ( { data} ) => win . emit ( `filemanager:${ data . isFile ? 'open' : 'navigate' } ` , data ) ,
802+ activate : ( { data} ) =>
803+ data . forEach ( item =>
804+ win . emit ( `filemanager:${ item . isFile ? 'open' : 'navigate' } ` , item )
805+ ) ,
775806 contextmenu : args => win . emit ( 'filemanager:contextmenu' , args ) ,
776807 created : ( { el, data} ) => {
777808 if ( data . isFile ) {
@@ -793,7 +824,7 @@ const createApplication = (core, proc) => {
793824 */
794825const createWindow = ( core , proc ) => {
795826 let wired ;
796- const state = { currentFile : undefined , currentPath : undefined } ;
827+ const state = { currentFile : [ ] , currentPath : undefined } ;
797828 const { homePath, initialPath} = createInitialPaths ( core , proc ) ;
798829
799830 const title = core . make ( 'osjs/locale' ) . translatableFlat ( proc . metadata . title ) ;
@@ -812,13 +843,29 @@ const createWindow = (core, proc) => {
812843 const onDrop = ( ...args ) => vfs . drop ( ...args ) ;
813844 const onHome = ( ) => vfs . readdir ( homePath , 'clear' ) ;
814845 const onNavigate = ( ...args ) => vfs . readdir ( ...args ) ;
815- const onSelectItem = file => ( state . currentFile = file ) ;
816- const onSelectStatus = file => win . emit ( 'filemanager:status' , formatFileMessage ( file ) ) ;
846+ const onSelectItem = files => ( state . currentFile = files ) ;
847+ const onSelectStatus = files => win . emit ( 'filemanager:status' , formatStatusMessage ( files ) ) ;
817848 const onContextMenu = ( { ev, data} ) => createMenu ( { ev, name : 'edit' } , data , true ) ;
818849 const onReaddirRender = args => wired . setList ( args ) ;
819850 const onRefresh = ( ...args ) => vfs . refresh ( ...args ) ;
820- const onOpen = file => core . open ( file , { useDefault : true } ) ;
821- const onOpenWith = file => core . open ( file , { useDefault : true , forceDialog : true } ) ;
851+ const onOpen = files => {
852+ if ( ! Array . isArray ( files ) ) {
853+ files = [ files ] ;
854+ }
855+
856+ return files . forEach (
857+ file => core . open ( file , { useDefault : true } )
858+ ) ;
859+ } ;
860+ const onOpenWith = files => {
861+ if ( ! Array . isArray ( files ) ) {
862+ files = [ files ] ;
863+ }
864+
865+ return files . forEach (
866+ file => core . open ( file , { useDefault : true , forceDialog : true } )
867+ ) ;
868+ } ;
822869 const onHistoryPush = file => wired . history . push ( file ) ;
823870 const onHistoryClear = ( ) => wired . history . clear ( ) ;
824871 const onMenu = ( props , args ) => createMenu ( props , args || state . currentFile ) ;
@@ -829,11 +876,11 @@ const createWindow = (core, proc) => {
829876 const onMenuToggleMinimalistic = ( ) => wired . toggleMinimalistic ( ) ;
830877 const onMenuShowDate = ( ) => setSetting ( 'showDate' , ! proc . settings . showDate ) ;
831878 const onMenuShowHidden = ( ) => setSetting ( 'showHiddenFiles' , ! proc . settings . showHiddenFiles ) ;
832- const onMenuRename = file => dialog ( 'rename' , vfs . action , file ) ;
833- const onMenuDelete = file => dialog ( 'delete' , vfs . action , file ) ;
834- const onMenuDownload = ( ... args ) => vfs . download ( ... args ) ;
835- const onMenuCopy = item => clipboard . set ( item ) ;
836- const onMenuCut = item => clipboard . cut ( item ) ;
879+ const onMenuRename = files => dialog ( 'rename' , vfs . action , files ) ;
880+ const onMenuDelete = files => dialog ( 'delete' , vfs . action , files ) ;
881+ const onMenuDownload = ( files ) => vfs . download ( files ) ;
882+ const onMenuCopy = items => clipboard . set ( items ) ;
883+ const onMenuCut = items => clipboard . cut ( items ) ;
837884 const onMenuPaste = ( ) => clipboard . paste ( ) ;
838885
839886 return win
0 commit comments