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