Skip to content

Commit e80d4e2

Browse files
ajmeese7andersevenrud
authored andcommitted
Multiselect support (#22)
1 parent 5ad4859 commit e80d4e2

File tree

1 file changed

+107
-60
lines changed

1 file changed

+107
-60
lines changed

index.js

+107-60
Original file line numberDiff line numberDiff line change
@@ -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
*/
794825
const 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

Comments
 (0)