Mon 21 Jul 22:43:21 CEST 2025
This commit is contained in:
parent
11d1497f94
commit
50598220a3
420
js/term/widgets/filemanager.js
Normal file
420
js/term/widgets/filemanager.js
Normal file
|
@ -0,0 +1,420 @@
|
|||
/**
|
||||
** ==============================
|
||||
** O O O OOOO
|
||||
** O O O O O O
|
||||
** O O O O O O
|
||||
** OOOO OOOO O OOO OOOO
|
||||
** O O O O O O O
|
||||
** O O O O O O O
|
||||
** OOOO OOOO O O OOOO
|
||||
** ==============================
|
||||
** Dr. Stefan Bosse http://www.bsslab.de
|
||||
**
|
||||
** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
|
||||
** BY THE AUTHOR(S).
|
||||
** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
|
||||
** MODIFIED, OR OTHERWISE USED IN A CONTEXT
|
||||
** OUTSIDE OF THE SOFTWARE SYSTEM.
|
||||
**
|
||||
** $AUTHORS: Christopher Jeffrey and contributors, Stefan Bosse
|
||||
** $INITIAL: (C) 2013-2018, Christopher Jeffrey and contributors
|
||||
** $VERSION: 1.4.2
|
||||
**
|
||||
** $INFO:
|
||||
*
|
||||
* filemanager.js - file manager element for blessed
|
||||
*
|
||||
* Events: 'ioerror','cd','file'
|
||||
*
|
||||
* New options: okayButton, cancelButton, autohide, select (select emits file event),
|
||||
* noshow, arrows: {up:'[-]',down:'[+]',width:3,height:1,fg:'red',bg:'default'}},
|
||||
* box:{bg,border}, input:{fg,bg,border}
|
||||
*
|
||||
** $ENDOFINFO
|
||||
*/
|
||||
|
||||
var options = {
|
||||
version:'1.4.2'
|
||||
}
|
||||
/**
|
||||
* Modules
|
||||
*/
|
||||
var Comp = Require('com/compat');
|
||||
|
||||
var path = Require('path')
|
||||
, fs = Require('fs');
|
||||
|
||||
|
||||
var Node = Require('term/widgets/node');
|
||||
var List = Require('term/widgets/list');
|
||||
var Button = Require('term/widgets/button');
|
||||
var Helpers = Require('term/helpers');
|
||||
var Box = Require('term/widgets/box');
|
||||
var TextBox = Require('term/widgets/textbox');
|
||||
var Arrows = Require('term/widgets/arrows');
|
||||
var Screen = Require('term/widgets/screen');
|
||||
|
||||
/**
|
||||
* FileManager
|
||||
*/
|
||||
|
||||
function FileManager(options) {
|
||||
var self = this,
|
||||
bbox,
|
||||
off1=0,
|
||||
off2=0,
|
||||
arrows=options.arrows;
|
||||
|
||||
if (!instanceOf(this,Node)) {
|
||||
return new FileManager(options);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
options.parseTags = true;
|
||||
options.mouse = true;
|
||||
options.arrows=_; // optional arrows are handled here, not in list
|
||||
|
||||
if (options.parent == Screen.global) {
|
||||
// Screen overlay; adjust top/height settings
|
||||
bbox=Helpers.bbox(Screen.global,options);
|
||||
if (options.box) bbox.top += 1,bbox.height -= 2,bbox.width -= 4,bbox.left += 2;
|
||||
if (options.input) bbox.height -= 3;
|
||||
if (options.cancelButton||options.okayButton) bbox.height -= 1;
|
||||
if (arrows) bbox.width -= 4,bbox.left += 2;
|
||||
options.top=bbox.top;
|
||||
options.left=bbox.left;
|
||||
options.height=bbox.height;
|
||||
options.width=bbox.width;
|
||||
}
|
||||
|
||||
// options.label = ' {blue-fg}%path{/blue-fg} ';
|
||||
List.call(this, options);
|
||||
|
||||
options.arrows=arrows;
|
||||
|
||||
this.cwd = options.cwd || process.cwd();
|
||||
this.file = this.cwd;
|
||||
this.value = this.cwd;
|
||||
this.noshow = options.noshow;
|
||||
|
||||
if (options.parent == this.screen) {
|
||||
// Collect clickable elements of this widget
|
||||
this._clickable=this.screen.clickable;
|
||||
this.screen.clickable=[];
|
||||
|
||||
// compute for button positions
|
||||
bbox=Helpers.bbox(this.screen,options);
|
||||
if (options.cancelButton||options.okayButton) off1=2;
|
||||
if (options.input) off2=3;
|
||||
if (options.box)
|
||||
this._.box = new Box({
|
||||
top:bbox.top-2,
|
||||
width:bbox.width+8,
|
||||
left:bbox.left-4,
|
||||
height:bbox.height+3+off1+off2,
|
||||
hidden:options.hidden,
|
||||
border:options.box.border,
|
||||
style:{
|
||||
label:options.label,
|
||||
fg:options.box.fg,
|
||||
bg:options.box.bg||'white'
|
||||
}
|
||||
});
|
||||
if (this._.box) this.screen.append(this._.box);
|
||||
|
||||
if (options.input) {
|
||||
this._.input = new TextBox({
|
||||
screen: this.screen,
|
||||
top: bbox.top+bbox.height+(options.box?1:0)-1,
|
||||
height: options.input.border&&options.input.border.type=='line'?3:1,
|
||||
width: bbox.width,
|
||||
left: bbox.left,
|
||||
keys: options.input.mutable?true:undefined,
|
||||
vi: options.input.mutable?true:undefined,
|
||||
mouse: options.input.mutable?true:undefined,
|
||||
inputOnFocus: options.input.mutable?true:undefined,
|
||||
value: options.input.value||'<input>',
|
||||
hidden:options.hidden,
|
||||
border: options.input.border,
|
||||
style: {
|
||||
fg:options.input.fg||'black',
|
||||
bg:options.input.bg||'white',
|
||||
bold:true
|
||||
}
|
||||
});
|
||||
this.screen.append(this._.input);
|
||||
}
|
||||
|
||||
if (options.okayButton) {
|
||||
this._.okay = new Button({
|
||||
screen: this.screen,
|
||||
top: bbox.top+bbox.height+(options.box?1:0)+off2,
|
||||
height: 1,
|
||||
left: bbox.left+1,
|
||||
width: 10,
|
||||
content: options.okayButton,
|
||||
align: 'center',
|
||||
style: {
|
||||
fg:'white',
|
||||
bg: 'blue',
|
||||
bold:true,
|
||||
},
|
||||
autoFocus: false,
|
||||
hidden:options.hidden,
|
||||
mouse: true
|
||||
});
|
||||
this._.okay.on('press',function () {
|
||||
var item=self.items[self.selected],
|
||||
value = self._.input?
|
||||
self._.input.getValue():
|
||||
item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, ''),
|
||||
file=path.resolve(self.cwd, value);
|
||||
self.emit('file', file);
|
||||
self.hide();
|
||||
});
|
||||
this.screen.append(this._.okay);
|
||||
}
|
||||
if (options.cancelButton) {
|
||||
this._.cancel = new Button({
|
||||
screen: this.screen,
|
||||
top: bbox.top+bbox.height+(options.box?1:0)+off2,
|
||||
height: 1,
|
||||
left: bbox.left+bbox.width-10-1,
|
||||
width: 10,
|
||||
content: options.cancelButton,
|
||||
align: 'center',
|
||||
style: {
|
||||
fg:'white',
|
||||
bg: 'red',
|
||||
bold:true,
|
||||
},
|
||||
autoFocus: false,
|
||||
hidden:options.hidden,
|
||||
mouse: true
|
||||
});
|
||||
this._.cancel.on('press',function () { self.hide(); });
|
||||
this.screen.append(this._.cancel);
|
||||
}
|
||||
if (options.arrows)
|
||||
Arrows(
|
||||
self,
|
||||
options,
|
||||
function () {self.emit('element wheelup')},
|
||||
function () {self.emit('element wheeldown')},
|
||||
true
|
||||
);
|
||||
this._hide=this.hide;
|
||||
this.hide = function() {
|
||||
self._hide();
|
||||
if (self._.box) self._.box.hide();
|
||||
if (self._.input) self._.input.hide();
|
||||
if (self._.okay) self._.okay.hide();
|
||||
if (self._.cancel) self._.cancel.hide();
|
||||
if (self._.up) self._.up.hide();
|
||||
if (self._.down) self._.down.hide();
|
||||
self.screen.render();
|
||||
// restore all clickable elements
|
||||
self.screen.clickable=self._clickable;
|
||||
}
|
||||
this._show = this.show;
|
||||
this.show = function() {
|
||||
// save all screen clickable elements; enable only this clickables
|
||||
self._clickable=self.screen.clickable;
|
||||
self.screen.clickable=self.clickable;
|
||||
self._show();
|
||||
if (self._.box) self._.box.show();
|
||||
if (self._.input) self._.input.show();
|
||||
if (self._.okay) self._.okay.show();
|
||||
if (self._.cancel) self._.cancel.show();
|
||||
if (self._.up) self._.up.show();
|
||||
if (self._.down) self._.down.show();
|
||||
self.screen.render();
|
||||
}
|
||||
|
||||
// Save clickable elements of this widget; restore screen
|
||||
this.clickable=this.screen.clickable;
|
||||
this.screen.clickable=this._clickable;
|
||||
}
|
||||
if (options.label && ~options.label.indexOf('%path')) {
|
||||
this._label.setContent(options.label.replace('%path', this.cwd));
|
||||
}
|
||||
if (this._.input)
|
||||
this.on('selected', function(item) {
|
||||
var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '');
|
||||
if (value.indexOf('/') != -1) value='';
|
||||
self._.input.setValue(value);
|
||||
self._.input.update();
|
||||
});
|
||||
|
||||
|
||||
this.on('select', function(item) {
|
||||
var value = item.content.replace(/\{[^{}]+\}/g, '').replace(/@$/, '')
|
||||
, file = path.resolve(self.cwd, value);
|
||||
return fs.stat(file, function(err, stat) {
|
||||
var _cwd=self.cwd;
|
||||
if (err) {
|
||||
return self.emit('ioerror', err, file);
|
||||
}
|
||||
self.file = file;
|
||||
self.value = file;
|
||||
if (stat.isDirectory()) {
|
||||
self.cwd = file;
|
||||
self.refresh(undefined,function (err) {
|
||||
if (err) self.cwd=_cwd;
|
||||
else if (options.label && ~options.label.indexOf('%path')) {
|
||||
self._label.setContent(options.label.replace('%path', self.cwd));
|
||||
self.emit('cd', file, self.cwd);
|
||||
self.screen.render();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (self.options.select) self.emit('file', file);
|
||||
if (self.options.select && self.options.autohide) self.hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
//FileManager.prototype.__proto__ = List.prototype;
|
||||
inheritPrototype(FileManager,List);
|
||||
|
||||
FileManager.prototype.type = 'file-manager';
|
||||
|
||||
FileManager.prototype.refresh = function(cwd, callback) {
|
||||
var self = this;
|
||||
|
||||
if (cwd) this.cwd = cwd;
|
||||
else cwd = this.cwd;
|
||||
|
||||
return fs.readdir(cwd, function(err, list) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
self.cwd = cwd !== process.env.HOME
|
||||
? process.env.HOME
|
||||
: '/';
|
||||
return self.refresh(undefined,callback);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (callback) return callback(err);
|
||||
return self.emit('ioerror', err, cwd);
|
||||
}
|
||||
|
||||
var dirs = []
|
||||
, files = [];
|
||||
|
||||
list.unshift('..');
|
||||
|
||||
list.forEach(function(name) {
|
||||
var f = path.resolve(cwd, name)
|
||||
, stat;
|
||||
|
||||
try {
|
||||
stat = fs.lstatSync(f);
|
||||
} catch (e) {
|
||||
;
|
||||
}
|
||||
|
||||
if ((stat && stat.isDirectory()) || name === '..') {
|
||||
dirs.push({
|
||||
name: name,
|
||||
text: '{light-blue-fg}' + name + '{/light-blue-fg}/',
|
||||
dir: true
|
||||
});
|
||||
} else if (stat && stat.isSymbolicLink()) {
|
||||
files.push({
|
||||
name: name,
|
||||
text: '{light-cyan-fg}' + name + '{/light-cyan-fg}@',
|
||||
dir: false
|
||||
});
|
||||
} else {
|
||||
files.push({
|
||||
name: name,
|
||||
text: name,
|
||||
dir: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
dirs = Helpers.asort(dirs);
|
||||
files = Helpers.asort(files);
|
||||
|
||||
list = dirs.concat(files).map(function(data) {
|
||||
return data.text;
|
||||
});
|
||||
|
||||
self.setItems(list);
|
||||
self.select(0);
|
||||
self.screen.render();
|
||||
|
||||
self.emit('refresh');
|
||||
|
||||
if (callback) callback();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
FileManager.prototype.pick = function(cwd, callback) {
|
||||
if (!callback) {
|
||||
callback = cwd;
|
||||
cwd = null;
|
||||
}
|
||||
var self = this
|
||||
, focused = this.screen.focused === this
|
||||
, hidden = this.hidden
|
||||
, onfile
|
||||
, oncancel;
|
||||
|
||||
function resume() {
|
||||
self.removeListener('file', onfile);
|
||||
self.removeListener('cancel', oncancel);
|
||||
if (hidden) {
|
||||
self.hide();
|
||||
}
|
||||
if (!focused) {
|
||||
self.screen.restoreFocus();
|
||||
}
|
||||
self.screen.render();
|
||||
}
|
||||
|
||||
this.on('file', onfile = function(file) {
|
||||
resume();
|
||||
return callback(null, file);
|
||||
});
|
||||
|
||||
this.on('cancel', oncancel = function() {
|
||||
resume();
|
||||
return callback();
|
||||
});
|
||||
|
||||
this.refresh(cwd, function(err) {
|
||||
if (err) return callback(err);
|
||||
|
||||
if (hidden) {
|
||||
self.show();
|
||||
}
|
||||
|
||||
if (!focused) {
|
||||
self.screen.saveFocus();
|
||||
self.focus();
|
||||
}
|
||||
|
||||
self.screen.render();
|
||||
});
|
||||
};
|
||||
|
||||
FileManager.prototype.reset = function(cwd, callback) {
|
||||
if (!callback) {
|
||||
callback = cwd;
|
||||
cwd = null;
|
||||
}
|
||||
this.cwd = cwd || this.options.cwd;
|
||||
this.refresh(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
module.exports = FileManager;
|
Loading…
Reference in New Issue
Block a user