Skip to content

Instantly share code, notes, and snippets.

@Pomax
Created March 10, 2015 02:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pomax/7c573623594043da389e to your computer and use it in GitHub Desktop.
Save Pomax/7c573623594043da389e to your computer and use it in GitHub Desktop.
Save as "browse.js", run with "node browse", instant image folder browser. Because I'm lazy
/**
* First things first: I am *lazy*. I will write a tool to do the thing I want to
* do so I don't have to do it a second time, ever. Or until a new programming
* language comes around. Generally the latter, but enough about that:
*
* This is a Node.js utility for taking the dir you are in right now, firing up
* an express server, and giving you image browsing on localhost:8080 with simple
* left/right controls once you're actually viewing images, and "up" control to
* go up a directory.
*
* I am, in fact, so lazy, that I didn't even want to bother with a package.json
* and a dependency list, so instead this util will install its own dependencies
* if they're not found, and then delete them again when you shut it down.
*
*/
var fs = require("fs");
var spawn = require("child_process").spawn;
var cleanup = (function installDependencies(install) {
if(!install) { return runServer(); }
var npm = process.platform === "win32" ? "npm.cmd" : "npm";
function cleanup(cb) {
console.log("cleaning up temporary dependencies...");
var ps = spawn("rm", ["-rf", "node_modules"]);
ps.on("close", function(code) { cb(); });
}
console.log("Temporarily installing dependencies...");
var ps = spawn(npm, ["install", "express"]);
ps.on("close", function(code) {
if(code===0) { runServer(); } else { cleanup(); }
});
return cleanup;
}(!fs.existsSync("node_modules")));
var originals = {};
var stylesheet = [
"<style>",
"html,body { margin: 0; padding:0; text-align:center; background:#EEE; }",
"img { max-height: calc(99vh - 1em); max-width: 80vw; margin: 0 -4px; }",
"button.n { min-height: 99vh; max-height: 99vh; vertical-align: top; border:none; }",
"button.u { display:block; width: 100%; height:1em; vertical-align: top; border:none; }",
"</style>"
].join("\n");
// cursor navigation, in case buttons didn't work
function cursornav() {
goPrev= function() { if(!!prev) window.location = prev; };
goNext = function() { if(!!next) window.location = next; };
goUp = function() {
var ondir = !window.location.pathname.match(/\.(jpg|png)$/);
var goto = ondir ? "./.." : ".";
window.location = goto;
};
document.addEventListener("keydown", function(evt) {
var key = evt.key;
evt.preventDefault();
evt.stopPropagation();
if(key === "Left" || key === "ArrowLeft") goPrev();
if(key === "Right" || key === "ArrowRight") goNext();
if(key === "Up" || key === "UpRight") goUp();
});
}
// a uniform function for making URLs web- and express-safe
function despacify(v) { return v.replace(/ /g,'_'); }
// What's in this dir???
function showContent(dir, req, res, cb) {
fs.readdir(dir, function(err, content) {
var newcontent = [
"<button class='u' onclick='goUp()'>^</button>"
].concat(content.map(function(d,i) {
var loc = dir + "/" + d,
rloc = despacify(loc);
if(rloc != loc) { originals[rloc] = loc; }
if(!rloc.match(/(jpg|png)$/)) { rloc += "/"; }
return "<a href='/"+rloc+"'>/"+d+"</a>";
})).join("<br>\n")
if(res) res.send([
"<script>(" + cursornav.toString() + "())</script>",
newcontent
].join("\n"));
if(cb) cb(content);
});
}
// a very simple HTML document builder
function formContent(prev, next, src, ext) {
return [
"<!doctype html><html><head><meta charset='utf-8'></head><body>",
stylesheet,
"<script>var prev = '" + prev + "';</script>",
"<script>var next = '" + next + "';</script>",
"<script>(" + cursornav.toString() + "())</script>",
"<button class='u' onclick='goUp()'>^</button>",
"<button class='n' onclick='goPrev()'>&lt;</button>",
"<img style='height:100%' src='data:image/" + ext + ";base64," + src.toString('base64') + "'>",
"<button class='n' onclick='goNext()'>&gt;</button>",
"</body></html>"
].join('\n')
}
// index the filesystem, binding express app routes as we go
function buildTree(app, dir) {
var route = despacify(dir.replace(".",''));
if(!route) route = "/";
app.get(route, function(req, res) {
showContent(dir, req, res);
});
// recursively get to know the filesystem. We want to know
// all about that lovely directory structure.
var content = fs.readdirSync(dir);
content.forEach(function(loc, i) {
var newloc = dir + "/" + loc;
if(fs.lstatSync(newloc).isDirectory()) {
if(loc.indexOf("node_modules")>-1) return;
buildTree(app, newloc);
}
});
// And finally, a "fallback" handler for resource requests
// in this directory, because those are going to be files,
// and we're cool with that. We like those files.
app.get(route + "/:file", function(req, res) {
showContent(dir, false, false, function(files) {
var filename = originals["." + req.params.file];
if(!filename) filename = req.params.file;
// Depending on the file's spot in the dir listing, we
// could have adjacent files that we could navigate to:
var ls = filename.lastIndexOf("/"),
fnonly = filename.substring(ls+1),
pos = files.indexOf(fnonly),
fprev = files[pos-1],
prev = pos>0 ? despacify(fprev) : '',
fnext = files[pos+1],
next = pos<files.length-2 ? despacify(fnext) : '',
content, src;
// FIXME: not every "prev" and "next" is actually a file.
try {
// but also: this file might not exist. Let's keep that in mind...
src = fs.readFileSync("./" + filename);
var ext = filename.substring(filename.lastIndexOf("."));
content = formContent(prev, next, src, ext);
} catch (e) {
console.error("404 - "+filename);
content = JSON.stringify(e,false,2);
} finally {
res.send(content);
}
});
});
}
// index the filesystem, then fire up our express server
function runServer() {
var express = require("express"), app = express();
console.log("building tree...");
buildTree(app, ".");
console.log("tree built.");
var port = 8080;
var ppos = process.argv.indexOf("-p");
if(ppos>-1) { port = parseInt(process.argv[ppos+1],10); }
app.param("file", function(req, res, next, file) {
req.params.file = req.path;
next();
});
app.listen(port , function() {
console.log("server listening on port "+port);
});
return false;
}
// Hook into SIGINT so that a ctrl-c/cmd-c (equal opportunity, some people use OSX)
// first cleans up before we process.exit
process.on('SIGINT', function() {
if(cleanup) { cleanup(function() { process.exit(0); }); }
else { process.exit(0); }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment