From a76744fb1eb87183e27980d00c4708ca32222e24 Mon Sep 17 00:00:00 2001 From: echicken <> Date: Tue, 18 Aug 2015 03:56:07 +0000 Subject: [PATCH] Lightbar filesystem browser object. --- exec/load/filebrowser.js | 372 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 exec/load/filebrowser.js diff --git a/exec/load/filebrowser.js b/exec/load/filebrowser.js new file mode 100644 index 0000000000..fee142bc62 --- /dev/null +++ b/exec/load/filebrowser.js @@ -0,0 +1,372 @@ +/* FileBrowser object + echicken -at- bbs.electronicchicken.com + + Options: + + var fileBrowser = new FileBrowser( + { 'path' : String, + 'top' : String (optional), + 'frame' : Frame (optional), + 'hide' : Array (optional), + 'colors' : Object (optional) + } + ); + + - 'path' is the directory to start in + - If 'top' is supplied, 'path' must be below 'top' in the filesystem + + - 'top' is the top-level directory that the browser will not ascend past + - If not supplied, 'top' will default to the same value as 'path' + + - 'frame' is an optional instance of Frame (see frame.js) to place the + browser inside of + - If 'frame' is omitted, the browser will fill the terminal window + + - 'hide' is an optional array of wildcard-pattern strings; files + matching these patterns (eg. "*.secret") will be omitted from + directory listings + - These patterns are not case-sensitive + - The * and ? wildcards are supported + - Patterns are applied to the filenames rather than full paths + + - 'colors' is an optional object with the following optional properties: + - 'fg' - foreground color of a non-highlit item + - 'bg' - background color of a non-highlit item + - 'lfg' - foreground color of a highlit item + - 'lbg' - background color of a highlit item + - 'sfg' - foreground color of the path bar ("Browsing: ...") + - 'sbg' - background color of the path bar ("Browsing: ...") + - See sbbsdefs.js for colors + + Properties: + + - 'path' - The directory currently being browsed (read-only) + + Events: + + - 'load' is an event that fires after the directory listing has been + read but before the file list has been displayed. Its callback + function will be given one argument, an array of strings of the + complete paths of all files and subdirectories within the current + directory. + + - 'file' is an event that fires once for each file in the list, just + prior to the file being displayed. Its callback function will receive + a string argument, the complete path to the file or subdirectory. + The return value of this function will be the text displayed in the + list for this file. + - The default callback function strips the path from the + filename, and wraps subdirectories in [ and ] + + - 'fileSelect' is an event that fires when a file is selected. Its + callback function will be given one string argument: the full path to + the file. + - The .refresh() method will be called after this event's callback + function returns + + Methods: + + - 'open' + - Fetch the file list and display the browser + + - 'on(event, callback)' + - Register a callback function for an event. See 'Events' above for + a list of events, and what arguments their callback functions will + receive. + - You can use the 'this' keyword within any callback function to + reference to the FileBrowser object that called the function. + + - 'getcmd(str)' + - Handle user input string 'str' + + - 'cycle' + - Update the scrollbar + - Update the display (if 'options.frame' was not provided) + - If 'options.frame' was given, it is assumed that the parent + script is calling Frame.cycle() at regular intervals on + 'options.frame' or its parent frame. + + - 'refresh' + - Force a redraw of the browser + + - 'close' + - Close / remove the browser from the display + + Example: + + // Hit 'Q' to quit + + load("sbbsdefs.js"); + load("filebrowser.js"); + + var fb = new FileBrowser( + { 'path' : system.text_dir + "menu", + 'top' : system.text_dir, + 'colors' : { + 'lfg' : WHITE, + 'lbg' : BG_CYAN + } + } + ); + fb.on( + "fileSelect", + function(fn) { + console.clear(); + console.printfile(fn); + console.pause(); + console.clear(); + } + ) + fb.open(); + + while(!js.terminated) { + var userInput = console.inkey(K_NONE, 5).toUpperCase(); + if(userInput == "Q") + break; + fb.getcmd(userInput); + fb.cycle(); + } +*/ + +load("sbbsdefs.js"); +load("frame.js"); +load("tree.js"); +load("scrollbar.js"); + +var FileBrowser = function(options) { + + var self = this; + var delimiter = backslash(system.exec_dir).substr(-1, 1); + + var properties = { + 'path' : "", + 'top' : null, + 'parentFrame' : null, + 'frame' : null, + 'pathFrame' : null, + 'tree' : null, + 'treeFrame' : null, + 'scrollBar' : null, + 'colors' : { + 'sfg' : WHITE, + 'sbg' : BG_BLUE, + 'fg' : WHITE, + 'bg' : BG_BLACK, + 'lfg' : WHITE, + 'lbg' : BG_CYAN + }, + 'hide' : [], + 'index' : 0 + }; + + var callbacks = { + 'load' : function(list) {}, + 'file' : function(str) { + if(file_isdir(str)) + return "[" + file_getname(str) + "]"; + return file_getname(str); + }, + 'fileSelect' : function() {} + }; + + var initOptions = function(options) { + + if( typeof options.path != "string" + || + !file_isdir(options.path) + ) { + throw "FileBrowser: invalid or no 'path' argument."; + } + + if(typeof options.frame != "undefined") + properties.parentFrame = options.frame; + + if(!file_isdir(options.path)) + throw "FileBrowser: 'path' argument must be an existing directory."; + else + properties.path = options.path; + + if( typeof options.top == "string" + && + ( !file_isdir(options.top) + || + options.top.length > options.path.length + ) + ) { + throw "FileBrowser: 'top' argument must be an existing directory above or equal to 'path'."; + } else if(typeof options.top == "string") { + properties.top = options.top; + } else { + properties.top = options.path; + } + + if(properties.path.substr(-1) != delimiter) + properties.path += delimiter; + + if(properties.top.substr(-1) != delimiter) + properties.top += delimiter; + + if(typeof options.colors == "object") { + for(var color in options.colors) + properties.colors[color] = options.colors[color]; + } + + if(typeof options.hide != "undefined") { + if(!Array.isArray(options.hide)) + throw "FileBrowser: 'hide' argument must be an array of strings."; + properties.hide = options.hide; + } + + } + + var initList = function() { + + properties.pathFrame.putmsg( + "Browsing: " + properties.path.replace(properties.top, delimiter) + ); + + // Would rather use GLOB_APPEND here, but it crashes my BBS. :| + files = [].concat( + directory( + properties.path + "*", + GLOB_ONLYDIR|GLOB_PERIOD + ), + directory( + properties.path + "*" + ).filter( + function(e) { + if(file_isdir(e)) + return false; + return true; + } + ) + ); + callbacks.load.apply(self, [files]); + + files.forEach( + function(item) { + var fileString = callbacks.file.apply(self, [item]); + if(fileString == "[.]") + return; + if(item.replace(/\.\.$/, "") == properties.top) + return; + var fn = file_getname(item); + for(var h = 0; h < properties.hide.length; h++) { + if(!wildmatch(fn, properties.hide[h])) + continue; + return; + } + properties.tree.addItem( + fileString, + file_isdir(item) + ? + (function() { + if(item.match(/\.\.$/) !== null) + item = item.split(delimiter).slice(0, -2).join(delimiter); + properties.path = backslash(item); + self.refresh(); + }) + : + (function() { + properties.index = properties.tree.index; + callbacks.fileSelect.apply(self, [item]); + self.refresh(); + properties.tree.index = properties.index; + properties.tree.refresh(); + }) + ); + } + ); + + properties.tree.index = 0; + + + } + + var initDisplay = function() { + + properties.frame = new Frame( + (properties.parentFrame === null) ? 1 : properties.parentFrame.x, + (properties.parentFrame === null) ? 1 : properties.parentFrame.y, + (properties.parentFrame === null) ? console.screen_columns : properties.parentFrame.width, + (properties.parentFrame === null) ? console.screen_rows : properties.parentFrame.height, + BG_BLACK|LIGHTGRAY, + (properties.parentFrame === null) ? undefined : properties.parentFrame + ); + + properties.pathFrame = new Frame( + properties.frame.x, + properties.frame.y, + properties.frame.width, + 1, + properties.colors.sbg|properties.colors.sfg, + properties.frame + ); + + properties.treeFrame = new Frame( + properties.frame.x, + properties.frame.y + 1, + properties.frame.width, + properties.frame.height - 1, + properties.frame.attr, + properties.frame + ); + + properties.tree = new Tree(properties.treeFrame); + for(var color in properties.colors) + properties.tree.colors[color] = properties.colors[color]; + initList(); + properties.scrollBar = new ScrollBar(properties.tree); + properties.frame.open(); + properties.tree.open(); + + } + + var init = function() { + initOptions(options); + initDisplay(); + } + + this.__defineGetter__("path", function() { return properties.path; }); + this.__defineSetter__("path", function() {}); + + this.open = function() { + init(); + properties.frame.draw(); + } + + this.on = function(event, callback) { + if(typeof event != "string" || typeof callbacks[event] == undefined) + throw "FileBrowser.on: Invalid or no event specified."; + if(typeof callback != "function") + throw "FileBrowser.on: Invalid callback function."; + callbacks[event] = callback; + } + + this.cycle = function() { + if(properties.parentFrame === null && properties.frame.cycle()) + console.gotoxy(console.screen_columns, console.screen_rows); + properties.scrollBar.cycle(); + } + + this.getcmd = function(cmd) { + properties.tree.getcmd(cmd); + } + + this.refresh = function() { + this.close(); + options = { + 'path' : properties.path, + 'top' : properties.top, + 'parentFrame' : properties.parentFrame + }; + this.open(); + } + + this.close = function() { + properties.tree.close(); + properties.scrollBar.close(); + properties.frame.close(); + } + +} -- GitLab