src/dialogs/file.js
/*
* OS.js - JavaScript Cloud/Web Desktop Platform
*
* Copyright (c) Anders Evenrud <andersevenrud@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Anders Evenrud <andersevenrud@gmail.com>
* @licence Simplified BSD License
*/
import {h, app} from 'hyperapp';
import Dialog from '../dialog';
import {
TextField,
SelectField,
listView
} from '@osjs/gui';
const getMountpoint = str => str
.split(':')[0] + ':/';
const getMountpoints = core => core.make('osjs/fs')
.mountpoints(true)
.filter(mount => {
return !(mount.attributes.readOnly && mount.attributes.visibility === 'restricted');
})
.reduce((mounts, iter) => Object.assign(mounts, {
[iter.root]: iter.label
}), {});
/**
* Default OS.js File Dialog
*/
export default class FileDialog extends Dialog {
/**
* Constructor
* @param {Core} core OS.js Core reference
* @param {Object} args Arguments given from service creation
* @param {String} [args.title] Dialog title
* @param {String} [args.type='open'] Dialog type (open/save)
* @param {String} [args.path] Current path
* @param {String} [args.filetype='file'] Dialog filetype (file/directory)
* @param {String} [args.filename] Current filename
* @param {String[]} [args.mime] Mime filter
* @param {Function} callback The callback function
*/
constructor(core, args, callback) {
args = Object.assign({}, {
title: null,
type: 'open',
filetype: 'file',
path: core.config('vfs.defaultPath'),
filename: null,
mime: []
}, args);
if (typeof args.path === 'string') {
args.path = {path: args.path};
}
try {
args.path = Object.assign({
isDirectory: true,
filename: args.path.path.split(':/')[1].split('/').pop() || ''
}, args.path);
} catch (e) {
console.warn(e);
}
const _ = core.make('osjs/locale').translate;
const title = args.title
? args.title
: (args.type === 'open' ? _('LBL_OPEN') : _('LBL_SAVE'));
super(core, args, {
className: 'file',
window: {
title,
attributes: {
resizable: true
},
dimension: {
width: 400,
height: 400
}
},
buttons: ['ok', 'cancel']
}, callback);
}
render(options) {
const getFileIcon = file => this.core.make('osjs/fs').icon(file);
const startingLocation = this.args.path;
super.render(options, ($content) => {
const a = app({
mount: startingLocation ? getMountpoint(startingLocation.path) : null,
filename: this.args.filename,
listview: listView.state({
columns: [{
label: 'Name'
}, {
label: 'Type'
}, {
label: 'Size'
}]
}),
buttons: {
ok: this.args.type === 'open' && this.args.filetype === 'directory'
? true
: !!this.args.filename
}
}, {
_readdir: ({path, files}) => (state, actions) => {
const listview = state.listview;
listview.selectedIndex = -1;
listview.rows = files.map(file => ({
columns: [{
label: file.filename,
icon: getFileIcon(file)
}, file.mime, file.humanSize],
data: file
}));
return {path, listview};
},
setButtonState: btn => state => ({
buttons: Object.assign({}, state.buttons, btn)
}),
setMountpoint: mount => (state, actions) => {
actions.setPath({path: mount});
return {mount};
},
setPath: file => async (state, actions) => {
const files = await this.core.make('osjs/vfs')
.readdir(file, {
filter: (item) => {
if (this.args.filetype === 'directory') {
return item.isDirectory === true;
} else if (this.args.mime.length) {
return item.mime
? this.args.mime.some(test => (new RegExp(test)).test(item.mime))
: true;
}
return true;
}
});
this.args.path = file;
actions._readdir({path: file.path, files});
if (this.args.filetype === 'file') {
actions.setButtonState({ok: this.args.type === 'save' ? !!this.args.filename : false});
}
},
setFilename: filename => state => ({filename}),
listview: listView.actions({
select: ({data}) => {
if (data.isFile) {
a.setFilename(data.filename);
}
this.value = data.isFile ? data : null;
if (this.args.filetype === 'file' && data.isFile) {
a.setButtonState({ok: true});
}
},
activate: ({data, ev}) => {
if (data.isDirectory) {
a.setPath(data);
} else {
this.value = data.isFile ? data : null;
this.emitCallback(this.getPositiveButton(), ev, true);
}
},
})
}, (state, actions) => this.createView([
h(SelectField, {
choices: getMountpoints(this.core),
onchange: (ev, val) => a.setMountpoint(val),
value: state.mount
}),
h(listView.component(Object.assign({
box: {grow: 1, shrink: 1}
}, state.listview), actions.listview)),
h(TextField, {
placeholder: this.args.filetype === 'directory' ? 'Directory Name' : 'File Name',
value: state.filename,
onenter: (ev, value) => this.emitCallback(this.getPositiveButton(), ev, true),
oninput: (ev) => {
const filename = ev.target.value;
actions.setButtonState({ok: !!filename});
actions.setFilename(filename);
},
box: {
style: {display: this.args.type === 'save' ? null : 'none'}
}
})
], state), $content);
a.setPath(startingLocation);
});
}
emitCallback(name, ev, close = false) {
if (this.calledBack) {
return;
}
const file = this.getValue();
const next = () => super.emitCallback(name, ev, close);
const isSave = this.args.type === 'save';
const buttonCancel = name === 'cancel';
const hasVfs = this.core.has('osjs/vfs');
const confirm = callback => this.core.make('osjs/dialog', 'confirm', {
message: `Do you want to overwrite ${file.path}?`
}, {
parent: this.win,
attributes: {modal: true}
}, (btn) => {
if (btn === 'yes') {
callback();
}
});
if (file && isSave && hasVfs && !buttonCancel) {
this.core
.make('osjs/vfs')
.exists(file)
.then(exists => {
if (exists) {
confirm(() => next());
} else {
next();
}
})
.catch(error => {
console.error(error);
next();
});
} else {
next();
}
}
getValue() {
if (this.args.type === 'save') {
const {path} = this.args.path;
const filename = this.win.$content.querySelector('input[type=text]')
.value;
return filename
? Object.assign({}, this.args.path, {
filename,
path: path.replace(/\/?$/, '/') + filename
})
: undefined;
} else {
if (this.args.filetype === 'directory') {
return this.args.path;
}
}
return super.getValue();
}
}