Created
March 10, 2015 02:56
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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()'><</button>", | |
"<img style='height:100%' src='data:image/" + ext + ";base64," + src.toString('base64') + "'>", | |
"<button class='n' onclick='goNext()'>></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