mirror of
https://github.com/openwrt/luci.git
synced 2026-02-04 12:06:01 +08:00
luci-base: extend FileUpload; create DirectoryPicker
FileUpload was extended to accommodate the new features, since it has nearly everything already. Create a DirectoryPicker convenience wrapper. Additions are: -directory creation (dialogue); set directory_create to true -directory select mode (instead of file); set directory_select to true Also fix a bug in the breadcrumb generation which produced: /foo » » bar for /foo/bar if root_directory is not '/', and another bug that merged links together when navigating upward again using the breadcrumbs. Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
@@ -4883,13 +4883,13 @@ const CBIHiddenValue = CBIValue.extend(/** @lends LuCI.form.HiddenValue.prototyp
|
||||
* offers the ability to browse, upload and select remote files.
|
||||
*
|
||||
* @param {LuCI.form.Map|LuCI.form.JSONMap} form
|
||||
* The configuration form to which this section is added to. It is automatically passed
|
||||
* The configuration form to which this section is added. It is automatically passed
|
||||
* by [option()]{@link LuCI.form.AbstractSection#option} or
|
||||
* [taboption()]{@link LuCI.form.AbstractSection#taboption} when adding the
|
||||
* option to the section.
|
||||
*
|
||||
* @param {LuCI.form.AbstractSection} section
|
||||
* The configuration section this option is added to. It is automatically passed
|
||||
* The configuration section this option is added. It is automatically passed
|
||||
* by [option()]{@link LuCI.form.AbstractSection#option} or
|
||||
* [taboption()]{@link LuCI.form.AbstractSection#taboption} when adding the
|
||||
* option to the section.
|
||||
@@ -4910,6 +4910,8 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
|
||||
this.super('__init__', args);
|
||||
|
||||
this.browser = false;
|
||||
this.directory_create = false;
|
||||
this.directory_select = false;
|
||||
this.show_hidden = false;
|
||||
this.enable_upload = true;
|
||||
this.enable_remove = true;
|
||||
@@ -4919,7 +4921,8 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
|
||||
|
||||
|
||||
/**
|
||||
* Open in a file browser mode instead of selecting for a file
|
||||
* Render the widget in browser mode initially instead of a button
|
||||
* to 'Select File...'.
|
||||
*
|
||||
* @name LuCI.form.FileUpload.prototype#browser
|
||||
* @type boolean
|
||||
@@ -4955,6 +4958,35 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
|
||||
* @default true
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle remote directory create functionality.
|
||||
*
|
||||
* When set to `true`, the underlying widget provides a button which lets
|
||||
* the user create directories. Note that this is merely
|
||||
* a cosmetic feature: remote create permissions are controlled by the
|
||||
* session ACL rules.
|
||||
*
|
||||
* The default of `false` means the directory create button is hidden.
|
||||
*
|
||||
* @name LuCI.form.FileUpload.prototype#directory_create
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle remote directory select functionality.
|
||||
*
|
||||
* When set to `true`, the underlying widget changes behaviour to select
|
||||
* directories instead of files, in effect, becoming a directory
|
||||
* picker.
|
||||
*
|
||||
* The default is `false`.
|
||||
*
|
||||
* @name LuCI.form.FileUpload.prototype#directory_select
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle remote file delete functionality.
|
||||
*
|
||||
@@ -5001,6 +5033,8 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
|
||||
name: this.cbid(section_id),
|
||||
browser: this.browser,
|
||||
show_hidden: this.show_hidden,
|
||||
directory_create: this.directory_create,
|
||||
directory_select: this.directory_select,
|
||||
enable_upload: this.enable_upload,
|
||||
enable_remove: this.enable_remove,
|
||||
enable_download: this.enable_download,
|
||||
@@ -5012,6 +5046,165 @@ const CBIFileUpload = CBIValue.extend(/** @lends LuCI.form.FileUpload.prototype
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @class DirectoryPicker
|
||||
* @memberof LuCI.form
|
||||
* @augments LuCI.form.Value
|
||||
* @hideconstructor
|
||||
* @classdesc
|
||||
*
|
||||
* The `DirectoryPicker` element wraps a {@link LuCI.ui.FileUpload} widget and
|
||||
* offers the ability to browse, create, delete and select remote directories.
|
||||
*
|
||||
* @param {LuCI.form.Map|LuCI.form.JSONMap} form
|
||||
* The configuration form to which this section is added. It is automatically passed
|
||||
* by [option()]{@link LuCI.form.AbstractSection#option} or
|
||||
* [taboption()]{@link LuCI.form.AbstractSection#taboption} when adding the
|
||||
* option to the section.
|
||||
*
|
||||
* @param {LuCI.form.AbstractSection} section
|
||||
* The configuration section this option is added. It is automatically passed
|
||||
* by [option()]{@link LuCI.form.AbstractSection#option} or
|
||||
* [taboption()]{@link LuCI.form.AbstractSection#taboption} when adding the
|
||||
* option to the section.
|
||||
*
|
||||
* @param {string} option
|
||||
* The name of the UCI option to map.
|
||||
*
|
||||
* @param {string} [title]
|
||||
* The title caption of the option element.
|
||||
*
|
||||
* @param {string} [description]
|
||||
* The description text of the option element.
|
||||
*/
|
||||
const CBIDirectoryPicker = CBIValue.extend(/** @lends LuCI.form.DirectoryPicker.prototype */ {
|
||||
__name__: 'CBI.DirectoryPicker',
|
||||
|
||||
__init__(...args) {
|
||||
this.super('__init__', args);
|
||||
|
||||
this.browser = false;
|
||||
this.directory_create = false;
|
||||
this.enable_download = false;
|
||||
this.enable_remove = false;
|
||||
this.enable_upload = false;
|
||||
this.root_directory = '/tmp';
|
||||
this.show_hidden = true;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Render the widget in browser mode initially instead of a button
|
||||
* to 'Select Directory...'.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#browser
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle remote directory create functionality.
|
||||
*
|
||||
* When set to `true`, the underlying widget provides a button which lets
|
||||
* the user create directories. Note that this is merely
|
||||
* a cosmetic feature: remote create permissions are controlled by the
|
||||
* session ACL rules.
|
||||
*
|
||||
* The default of `false` means the directory create button is hidden.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#directory_create
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle download file functionality.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#enable_download
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle remote file delete functionality.
|
||||
*
|
||||
* When set to `true`, the underlying widget provides buttons which let
|
||||
* the user delete files from remote directories. Note that this is merely
|
||||
* a cosmetic feature: remote delete permissions are controlled by the
|
||||
* session ACL rules.
|
||||
*
|
||||
* The default is `false`, means file removal buttons are not displayed.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#enable_remove
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle file upload functionality.
|
||||
*
|
||||
* When set to `true`, the underlying widget provides a button which lets
|
||||
* the user select and upload local files to the remote system.
|
||||
* Note that this is merely a cosmetic feature: remote upload access is
|
||||
* controlled by the session ACL rules.
|
||||
*
|
||||
* The default of `false` means file upload functionality is disabled.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#enable_upload
|
||||
* @type boolean
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specify the root directory for file browsing.
|
||||
*
|
||||
* This property defines the topmost directory the file browser widget may
|
||||
* navigate to. The UI will not allow browsing directories outside this
|
||||
* prefix. Note that this is merely a cosmetic feature: remote file access
|
||||
* and directory listing permissions are controlled by the session ACL
|
||||
* rules.
|
||||
*
|
||||
* The default is `/tmp`.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#root_directory
|
||||
* @type string
|
||||
* @default /tmp
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle display of hidden files.
|
||||
*
|
||||
* Display hidden files when rendering the remote directory listing.
|
||||
* Note that this is merely a cosmetic feature: hidden files are always
|
||||
* included in received remote file listings.
|
||||
*
|
||||
* The default of `true` means hidden files are displayed.
|
||||
*
|
||||
* @name LuCI.form.DirectoryPicker.prototype#show_hidden
|
||||
* @type boolean
|
||||
* @default true
|
||||
*/
|
||||
|
||||
/** @private */
|
||||
renderWidget(section_id, option_index, cfgvalue) {
|
||||
const browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
|
||||
id: this.cbid(section_id),
|
||||
name: this.cbid(section_id),
|
||||
browser: this.browser,
|
||||
directory_create: this.directory_create,
|
||||
directory_select: true,
|
||||
enable_download: this.enable_download,
|
||||
enable_remove: this.enable_remove,
|
||||
enable_upload: this.enable_upload,
|
||||
root_directory: this.root_directory,
|
||||
show_hidden: this.show_hidden,
|
||||
disabled: (this.readonly != null) ? this.readonly : this.map.readonly
|
||||
});
|
||||
|
||||
return browserEl.render();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @class SectionValue
|
||||
* @memberof LuCI.form
|
||||
@@ -5202,5 +5395,6 @@ return baseclass.extend(/** @lends LuCI.form.prototype */ {
|
||||
Button: CBIButtonValue,
|
||||
HiddenValue: CBIHiddenValue,
|
||||
FileUpload: CBIFileUpload,
|
||||
DirectoryPicker: CBIDirectoryPicker,
|
||||
SectionValue: CBISectionValue
|
||||
});
|
||||
|
||||
@@ -2921,6 +2921,12 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
* remotely depends on the ACL setup for the current session. This option
|
||||
* merely controls whether the file remove controls are rendered or not.
|
||||
*
|
||||
* @property {boolean} [directory_create=false]
|
||||
* Specifies whether the widget allows the user to create directories.
|
||||
*
|
||||
* @property {boolean} [directory_select=false]
|
||||
* Specifies whether the widget shall select directories only instead of files.
|
||||
*
|
||||
* @property {boolean} [enable_download=false]
|
||||
* Specifies whether the widget allows the user to download files.
|
||||
*
|
||||
@@ -2935,6 +2941,8 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
this.value = value;
|
||||
this.options = Object.assign({
|
||||
browser: false,
|
||||
directory_create: false,
|
||||
directory_select: false,
|
||||
show_hidden: false,
|
||||
enable_upload: true,
|
||||
enable_remove: true,
|
||||
@@ -2960,15 +2968,17 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
const renderFileBrowser = L.resolveDefault(this.value != null ? fs.stat(this.value) : null).then(L.bind((stat) => {
|
||||
let label;
|
||||
|
||||
if (L.isObject(stat) && stat.type != 'directory')
|
||||
if (L.isObject(stat))
|
||||
this.stat = stat;
|
||||
|
||||
if (this.stat != null)
|
||||
if (this.stat != null && this.stat.type === 'directory')
|
||||
label = [ this.iconForType(this.stat.type), ' %s'.format(this.truncatePath(this.stat.path)) ];
|
||||
else if (this.stat != null && this.stat.type !== 'directory')
|
||||
label = [ this.iconForType(this.stat.type), ' %s (%1000mB)'.format(this.truncatePath(this.stat.path), this.stat.size) ];
|
||||
else if (this.value != null)
|
||||
label = [ this.iconForType('file'), ' %s (%s)'.format(this.truncatePath(this.value), _('File not accessible')) ];
|
||||
else
|
||||
label = [ _('Select file…') ];
|
||||
label = [ this.options.directory_select ? _('Select directory…') : _('Select file…') ];
|
||||
let btnOpenFileBrowser = E('button', {
|
||||
'class': 'btn open-file-browser',
|
||||
'click': UI.prototype.createHandlerFn(this, 'handleFileBrowser'),
|
||||
@@ -3051,13 +3061,65 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
if (cpath.length <= croot.length)
|
||||
return [ croot ];
|
||||
|
||||
const parts = cpath.substring(croot.length).split(/\//);
|
||||
const parts = cpath.substring(croot.length).split(/\//).filter(p => p !== '');
|
||||
|
||||
parts.unshift(croot);
|
||||
|
||||
return parts;
|
||||
},
|
||||
|
||||
/** @private */
|
||||
handleCreateDirectory(path, ev) {
|
||||
const container = E('div', { 'class': 'uci-dialog' });
|
||||
|
||||
const input = E('input', {
|
||||
'type': 'text',
|
||||
'placeholder': _('Directory name'),
|
||||
'style': 'margin-right: 0.5em'
|
||||
});
|
||||
|
||||
const okBtn = E('button', {
|
||||
'type': 'button',
|
||||
'class': 'btn cbi-button',
|
||||
'click': async () => {
|
||||
var directoryName = input.value.trim();
|
||||
if (!directoryName) {
|
||||
alert(_('Directory name cannot be empty.'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Assume current upload path (you may need to retrieve or set this yourself)
|
||||
var basePath = path || '/tmp';
|
||||
var fullPath = basePath + '/' + directoryName;
|
||||
|
||||
await fs.exec('mkdir', ['-p', fullPath]).then(L.bind((path, ev) => {
|
||||
return this.handleSelect(path, null, ev);
|
||||
}, this, path, ev));
|
||||
} catch (err) {
|
||||
UI.prototype.addTimeLimitedNotification(_('Error'), E('p', _('Failed to create directory: %s').format(err.message)), 5000, 'error');
|
||||
} finally {
|
||||
UI.prototype.hideModal();
|
||||
}
|
||||
}
|
||||
}, _('OK'));
|
||||
|
||||
var cancelBtn = E('button', {
|
||||
'type': 'button',
|
||||
'class': 'btn cbi-button',
|
||||
'click': () => UI.prototype.hideModal(),
|
||||
}, _('Cancel'));
|
||||
|
||||
container.appendChild(input);
|
||||
container.appendChild(okBtn);
|
||||
container.appendChild(cancelBtn);
|
||||
|
||||
|
||||
UI.prototype.showModal(_('Create Directory'), [
|
||||
container
|
||||
]);
|
||||
},
|
||||
|
||||
/** @private */
|
||||
handleUpload(path, list, ev) {
|
||||
const form = ev.target.parentNode;
|
||||
@@ -3115,7 +3177,7 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
const hidden = this.node.lastElementChild;
|
||||
|
||||
if (path == hidden.value) {
|
||||
dom.content(button, _('Select file…'));
|
||||
dom.content(button, this.options.directory_select ? _('Select directory…') : _('Select file…'));
|
||||
hidden.value = '';
|
||||
}
|
||||
|
||||
@@ -3196,6 +3258,8 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
E('div', { 'class': 'name' }, [
|
||||
this.iconForType(list[i].type),
|
||||
' ',
|
||||
(this.options.directory_select && list[i].type !== 'directory') ?
|
||||
list[i].name :
|
||||
E('a', {
|
||||
'href': '#',
|
||||
'style': selected ? 'font-weight:bold' : null,
|
||||
@@ -3213,6 +3277,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
mtime.getSeconds())
|
||||
]),
|
||||
E('div', [
|
||||
(this.options.directory_select && list[i].type === 'directory') ? E('button', {
|
||||
'class': 'btn cbi-button',
|
||||
'click': UI.prototype.createHandlerFn(this, 'handleSelect',
|
||||
entrypath, list[i].type === 'directory' ? list[i] : null)
|
||||
}, [ _('Select') ]) : '',
|
||||
selected ? E('button', {
|
||||
'class': 'btn',
|
||||
'click': UI.prototype.createHandlerFn(this, 'handleReset')
|
||||
@@ -3236,7 +3305,7 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
let cur = '';
|
||||
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
cur += dirs[i];
|
||||
cur = (i === 0 || cur === '/') ? cur + dirs[i] : cur + '/' + dirs[i];
|
||||
dom.append(breadcrumb, [
|
||||
i ? ' » ' : '',
|
||||
E('a', {
|
||||
@@ -3251,6 +3320,11 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
rows,
|
||||
E('div', { 'class': 'right' }, [
|
||||
this.renderUpload(path, list),
|
||||
(this.options.directory_create) ? E('a', {
|
||||
'href': '#',
|
||||
'class': 'btn cbi-button',
|
||||
'click': UI.prototype.createHandlerFn(this, 'handleCreateDirectory', path)
|
||||
}, _('Create')) : '',
|
||||
!this.options.browser ? E('a', {
|
||||
'href': '#',
|
||||
'class': 'btn',
|
||||
@@ -3279,7 +3353,7 @@ const UIFileUpload = UIElement.extend(/** @lends LuCI.ui.FileUpload.prototype */
|
||||
const hidden = this.node.lastElementChild;
|
||||
|
||||
hidden.value = '';
|
||||
dom.content(button, _('Select file…'));
|
||||
dom.content(button, this.options.directory_select ? _('Select directory…') : _('Select file…'));
|
||||
|
||||
this.handleCancel(ev);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user