| @@ -23,7 +23,10 @@ var endpoints = { | |||
| //Viewer files | |||
| "/viewer": "viewer/index.node.js", | |||
| "/viewer/script.js": "viewer/script.js", | |||
| "/viewer/style.css": "viewer/style.css" | |||
| "/viewer/style.css": "viewer/style.css", | |||
| //API files | |||
| "/api/upload": "api/upload.node.js" | |||
| } | |||
| var loaded = loader.load(endpoints, conf); | |||
| @@ -1,3 +1,5 @@ | |||
| var formidable = require("formidable"); | |||
| function templatify(str, args) { | |||
| if (args == undefined) | |||
| return str; | |||
| @@ -22,6 +24,19 @@ module.exports.prototype = { | |||
| this.res.end(str); | |||
| }, | |||
| succeed: function(obj) { | |||
| obj = obj || {}; | |||
| obj.success = true; | |||
| this.end(JSON.stringify(obj)); | |||
| }, | |||
| fail: function(err) { | |||
| obj = obj || {}; | |||
| obj.success = false; | |||
| obj.error = error; | |||
| this.end(JSON.stringify(obj)); | |||
| }, | |||
| template: function(name, args) { | |||
| var str = this.templates[name]; | |||
| if (!str) | |||
| @@ -39,27 +54,10 @@ module.exports.prototype = { | |||
| }, | |||
| getPostData: function(cb) { | |||
| if (this.req.method != "POST") | |||
| if (this.req.method.toUpperCase() != "POST") | |||
| return cb(new Error("Expected POST request, got "+this.req.method)); | |||
| if (this._postData) | |||
| return cb(null, this._postData); | |||
| var str = ""; | |||
| this.req.on("data", function(data) { | |||
| str += data; | |||
| }); | |||
| this.req.on("end", function() { | |||
| try { | |||
| var obj = JSON.parse(str); | |||
| } catch (err) { | |||
| return cb(err); | |||
| } | |||
| this._postData = obj; | |||
| cb(null, obj); | |||
| }); | |||
| var form = new formidable.IncomingForm(); | |||
| form.parse(this.req, cb); | |||
| } | |||
| } | |||
| @@ -25,7 +25,9 @@ exports.load = function(endpoints, conf) { | |||
| res.endpoints[i] = fs.readFileSync(conf.webroot+"/"+ep, "utf8"); | |||
| //If it's an HTML file, we minify it | |||
| if (/\.html$/.test(ep)) { | |||
| if (!conf.minify) { | |||
| //Don't minify unless the conf tells us to | |||
| } else if (/\.html$/.test(ep)) { | |||
| res.endpoints[i] = minify.html(res.endpoints[i]); | |||
| } else if (/\.js$/.test(ep)) { | |||
| res.endpoints[i] = minify.js(res.endpoints[i]); | |||
| @@ -14,6 +14,7 @@ | |||
| }, | |||
| "license": "GPLv2", | |||
| "dependencies": { | |||
| "formidable": "^1.0.17", | |||
| "html-minifier": "^0.7.2", | |||
| "pg": "^4.4.0", | |||
| "uglify-js": "^2.4.24", | |||
| @@ -0,0 +1,22 @@ | |||
| CREATE TABLE users ( | |||
| id SERIAL PRIMARY KEY, | |||
| username VARCHAR(64) UNIQUE NOT NULL, | |||
| pass_hash CHAR(128) NOT NULL, | |||
| date_created TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW() | |||
| ) | |||
| CREATE TABLE collections ( | |||
| id SERIAL PRIMARY KEY, | |||
| name VARCHAR(64), | |||
| user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE | |||
| ) | |||
| CREATE TABLE images ( | |||
| id SERIAL PRIMARY KEY, | |||
| name VARCHAR(64) NOT NULL, | |||
| description TEXT, | |||
| extension VARCHAR(16) NOT NULL, | |||
| collection_id INTEGER NOT NULL REFERENCES collections(id) ON DELETE CASCADE | |||
| ) | |||
| @@ -13,7 +13,7 @@ | |||
| <button class="btn btn-default" onclick="$('#uploader-input').click()"> | |||
| Select Files | |||
| </button> | |||
| <button class="btn btn-default" id="uploader-upload"> | |||
| <button class="btn btn-default" id="uploader-upload" disabled> | |||
| Upload | |||
| </button> | |||
| @@ -0,0 +1,7 @@ | |||
| module.exports = function(ctx) { | |||
| ctx.getPostData(function(err, data, files) { | |||
| if (err) return console.log(err); | |||
| ctx.succeed(); | |||
| }); | |||
| } | |||
| @@ -30,4 +30,35 @@ | |||
| .replace(/>/g, "<") | |||
| .replace(/"/g, """); | |||
| } | |||
| util.api = function(name, data, cb, getXhr) { | |||
| var fd = new FormData(); | |||
| for (var i in data) { | |||
| console.log(i); | |||
| fd.append(i, data[i]); | |||
| } | |||
| return $.ajax({ | |||
| method: "POST", | |||
| url: "/api/"+name, | |||
| data: fd, | |||
| processData: false, | |||
| contentType: false, | |||
| xhr: function() { | |||
| var xhr = new XMLHttpRequest(); | |||
| if (getXhr) | |||
| getXhr(xhr); | |||
| return xhr; | |||
| } | |||
| }).done(function(res) { | |||
| var obj = JSON.parse(res); | |||
| if (obj.success) | |||
| cb(null, obj); | |||
| else | |||
| cb(obj.error); | |||
| }); | |||
| } | |||
| })(); | |||
| @@ -8,8 +8,10 @@ | |||
| files.forEach(function(f, i) { | |||
| output.push( | |||
| '<li class="file list-group-item" data-index='+i+'>'+ | |||
| '<span class="name">'+util.htmlEntities(f.name)+'</span>'+ | |||
| '<div class="progress-bar"></div>'+ | |||
| '<button class="btn btn-default delete" onclick="uploaderDelete(this.parentNode)">X</button>'+ | |||
| '<img class="thumbnail" src="'+f.thumbnail+'">'+ | |||
| '<span class="name">'+util.htmlEntities(f.name)+'</span>'+ | |||
| '</li>' | |||
| ); | |||
| }); | |||
| @@ -19,11 +21,27 @@ | |||
| var files = []; | |||
| $("#uploader-input").on("change", function(evt) { | |||
| //Enable upload button | |||
| $("#uploader-upload").removeAttr("disabled") | |||
| console.log("making uploader button not disabled"); | |||
| var inputFiles = evt.target.files; | |||
| for (var i = 0; i < inputFiles.length; ++i) { | |||
| for (var i = 0; i < inputFiles.length; ++i) (function() { | |||
| var f = inputFiles[i]; | |||
| f.thumbnail = ""; | |||
| var reader = new FileReader(); | |||
| reader.readAsDataURL(f); | |||
| reader.onload = function(evt) { | |||
| f.thumbnail = reader.result; | |||
| draw(files); | |||
| } | |||
| files.push(inputFiles[i]); | |||
| } | |||
| })(); | |||
| draw(files); | |||
| }); | |||
| @@ -34,7 +52,40 @@ | |||
| draw(files); | |||
| } | |||
| //Upload things when the upload button is clicked | |||
| $("#uploader-upload").on("click", function(evt) { | |||
| console.log(output); | |||
| //First, disable all buttons | |||
| $("#uploader button.btn").prop("disabled", true); | |||
| console.log("making buttons disabled"); | |||
| var elems = []; | |||
| $("#uploader-list .file").each(function() { | |||
| var elem = $(this); | |||
| elems[elem.data("index")] = elem; | |||
| }); | |||
| files.forEach(function(f, i) { | |||
| var progressBar = elems[i].children(".progress-bar"); | |||
| function getXhr(xhr) { | |||
| xhr.upload.addEventListener("progress", function(evt) { | |||
| if (!evt.lengthComputable) | |||
| return; | |||
| var percent = (evt.loaded / evt.total) * 100; | |||
| progressBar.css({width: percent+"%"}); | |||
| }, false); | |||
| } | |||
| var ajax = util.api("upload", { | |||
| name: f.name, | |||
| data: f | |||
| }, function(err, res) { | |||
| console.log(res); | |||
| }, getXhr); | |||
| }); | |||
| }); | |||
| })(); | |||
| @@ -1,11 +1,25 @@ | |||
| #uploader-list .file { | |||
| text-align: right !important; | |||
| #uploader-list .file .delete { | |||
| float: right; | |||
| margin-right: -5px; | |||
| } | |||
| #uploader-list .file .thumbnail { | |||
| display: inline; | |||
| margin: 0px; | |||
| margin-right: 10px; | |||
| height: 100px; | |||
| image-orientation: from-image; | |||
| } | |||
| #uploader-list .file .name { | |||
| float: left; | |||
| margin-top: 6px; | |||
| display: inline-block; | |||
| } | |||
| #uploader-list .file .progress-bar { | |||
| height: 0px; | |||
| position: absolute; | |||
| background: none; | |||
| border-top: 3px solid green; | |||
| margin-top: -3px; | |||
| max-width: calc(100% - 25px); | |||
| } | |||
| #uploader-upload { | |||
| float: right; | |||
| } | |||