Added albums API
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
29dafae886
commit
bfd872f60a
@ -10,22 +10,22 @@
|
||||
<dom-module id="photo-thumbnail">
|
||||
<template>
|
||||
<style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning">
|
||||
:host {
|
||||
@apply --photo-thumbnail;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
:host {
|
||||
@apply --photo-thumbnail;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
box-sizing: border-box;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
:host > div {
|
||||
padding: 0.5em;
|
||||
@ -34,12 +34,17 @@
|
||||
:host:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
div[path]:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="layout vertical">
|
||||
<div>[[item.id]]</div>
|
||||
<div>[[date(item)]]</div>
|
||||
<div>[[item.path]]</div>
|
||||
<div path on-tap="_pathTap">[[item.path]]</div>
|
||||
<div>[[item.filename]]</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -61,6 +66,10 @@
|
||||
}
|
||||
},
|
||||
|
||||
listeners: {
|
||||
"tap": "_imageTap"
|
||||
},
|
||||
|
||||
observers: [
|
||||
"widthChanged(width)",
|
||||
"thumbChanged(thumbpath)"
|
||||
@ -76,7 +85,7 @@
|
||||
},
|
||||
|
||||
safeItemThumbFilepath: function(item, base) {
|
||||
return "'" + (base + item.path + "/thumbs/" + item.filename).replace(/'/, "\\'") + "'";
|
||||
return "'" + (base + encodeURI(item.path) + "/thumbs/" + encodeURI(item.filename)).replace(/'/, "\\'") + "'";
|
||||
},
|
||||
|
||||
date: function(item) {
|
||||
@ -85,11 +94,15 @@
|
||||
},
|
||||
|
||||
_imageTap: function(event) {
|
||||
window.open(this.base + event.model.item.path + "/" + event.model.item.filename, "image");
|
||||
this.fire("load-image");
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
_pathTap: function(event) {
|
||||
window.location.href = event.model.item.filepath;
|
||||
this.fire("load-album", this.item.path);
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
|
@ -48,6 +48,15 @@
|
||||
:host {
|
||||
}
|
||||
|
||||
#breadcrumb > div {
|
||||
margin-right: 0.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#breadcrumb > div:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
app-toolbar {
|
||||
background-color: rgba(64, 0, 64, 0.5);
|
||||
color: white;
|
||||
@ -76,6 +85,10 @@
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
#header > * {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
app-header-layout {
|
||||
--layout-fit: {
|
||||
overflow-y: hidden !important;
|
||||
@ -113,15 +126,17 @@
|
||||
|
||||
<app-header-layout reveals>
|
||||
<app-header fixed>
|
||||
<div>
|
||||
<div id="header" class="layout horizontal center">
|
||||
<paper-spinner active$="[[loading]]" class="thin"></paper-spinner>
|
||||
<paper-radio-group selected="{{order}}">
|
||||
<paper-radio-button name="by-date">By date</paper-radio-button>
|
||||
<paper-radio-button name="by-album">By album</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
<paper-checkbox checked$="[[limitPerFolder]]" on-checked-changed="onLimitPerFolderChecked">Limit per folder</paper-checkbox>
|
||||
<paper-checkbox checked$="[[limitPerFolder]]" on-checked-changed="onLimitPerFolderChanged">Limit per folder</paper-checkbox>
|
||||
<paper-checkbox checked$="[[breakOnDayChange]]" on-checked-changed="onBreakOnDayChanged">Break on day change</paper-checkbox>
|
||||
<div>[[path]]</div>
|
||||
<div id="breadcrumb" class="horizontal layout center"><template is="dom-repeat" items="[[breadcrumb(path)]]">
|
||||
<div on-tap="loadPath">[[item.name]] /</div>
|
||||
</template></div>
|
||||
</div>
|
||||
</app-header>
|
||||
<div id="content">
|
||||
@ -174,11 +189,31 @@
|
||||
}
|
||||
},
|
||||
|
||||
breadcrumb: function(path) {
|
||||
var crumbs = path.split("/"), parts = [];
|
||||
path = "";
|
||||
crumbs.forEach(function(crumb, index) {
|
||||
if (crumb) {
|
||||
path += "/" + crumb;
|
||||
}
|
||||
parts.push({
|
||||
name: crumb ? crumb : "Top",
|
||||
path: path
|
||||
})
|
||||
});
|
||||
return parts;
|
||||
},
|
||||
|
||||
observers: [
|
||||
"widthChanged(calcWidth)"
|
||||
"widthChanged(calcWidth)",
|
||||
"orderChanged(order)"
|
||||
],
|
||||
|
||||
onLimitPerFolder: function(event) {
|
||||
orderChanged: function(order) {
|
||||
|
||||
},
|
||||
|
||||
onLimitPerFolderChanged: function(event) {
|
||||
if (!this.photos) {
|
||||
return;
|
||||
}
|
||||
@ -206,6 +241,25 @@
|
||||
"iron-resize" : "onResize"
|
||||
},
|
||||
|
||||
loadPath: function(event) {
|
||||
this.path = event.model.item.path;
|
||||
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
||||
this.photos = [];
|
||||
this.next = false;
|
||||
this._loadPhotos();
|
||||
},
|
||||
|
||||
loadAlbum: function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
this.path = event.detail;
|
||||
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
||||
this.photos = [];
|
||||
this.next = false;
|
||||
this._loadPhotos();
|
||||
},
|
||||
|
||||
onScroll: function(event) {
|
||||
if (this.disableScrolling) {
|
||||
event.preventDefault();
|
||||
@ -300,35 +354,79 @@
|
||||
},
|
||||
|
||||
loadNextPhotos: function() {
|
||||
if (!this.photos.length) {
|
||||
return;
|
||||
}
|
||||
var cursor = this.photos[this.photos.length - 1];
|
||||
this._loadPhotos(cursor.taken.toString().replace(/T.*/, "") + "_" + cursor.id, -1, true);
|
||||
},
|
||||
|
||||
loadPrevPhotos: function() {
|
||||
if (!this.photos.length) {
|
||||
return;
|
||||
}
|
||||
var cursor = this.photos[0];
|
||||
this._loadPhotos(cursor.taken.toString().replace(/T.*/, "") + "_" + cursor.id, +1);
|
||||
},
|
||||
|
||||
appendItems: function(photos) {
|
||||
photos.forEach(function(photo) {
|
||||
var thumbnail = document.createElement("photo-thumbnail");
|
||||
var thisDay;
|
||||
if (this.limitPerFolder) {
|
||||
console.log("Max per day: " + this.cols);
|
||||
}
|
||||
|
||||
thisDay = 0;
|
||||
for (var i = 0; i < photos.length; i++) {
|
||||
var photo = photos[i],
|
||||
thumbnail = document.createElement("photo-thumbnail"),
|
||||
datetime;
|
||||
thumbnail.item = photo;
|
||||
thumbnail.width = this.calcWidth;
|
||||
thumbnail.addEventListener("click", this._imageTap.bind(this));
|
||||
thumbnail.addEventListener("load-image", this._imageTap.bind(this));
|
||||
thumbnail.addEventListener("load-album", this.loadAlbum.bind(this));
|
||||
datetime = (photo.taken || photo.modified || photo.added).replace(/T.*$/, "");
|
||||
|
||||
if (this.breakOnDayChange) {
|
||||
var datetime = (photo.taken || photo.modified || photo.added).replace(/T.*$/, ""),
|
||||
dateBlock = this.querySelector("#date-" + datetime);
|
||||
var dateBlock = this.querySelector("#date-" + datetime);
|
||||
if (!dateBlock) {
|
||||
dateBlock = document.createElement("div");
|
||||
dateBlock.id = "date-" + datetime;
|
||||
dateBlock.classList.add("date-line");
|
||||
dateBlock.textContent = datetime;
|
||||
Polymer.dom(this.$.thumbnails).appendChild(dateBlock);
|
||||
thisDay = 0;
|
||||
} else {
|
||||
if (this.limitPerFolder) {
|
||||
var thumbs = [], el = dateBlock.nextElementSibling;
|
||||
while (el && el.tagName == "PHOTO-THUMBNAIL") {
|
||||
thumbs.push(el);
|
||||
el = el.nextElementSibling;
|
||||
}
|
||||
thisDay = thumbs.length;
|
||||
while (thisDay > this.cols) {
|
||||
Polymer.dom(thumbs[thisDay - 1].parentElement).removeChild(thumbs[thisDay - 1]);
|
||||
thisDay--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Polymer.dom(this.$.thumbnails).appendChild(thumbnail);
|
||||
}.bind(this));
|
||||
if (!this.limitPerFolder || thisDay < this.cols) {
|
||||
Polymer.dom(this.$.thumbnails).appendChild(thumbnail);
|
||||
thisDay++;
|
||||
}
|
||||
|
||||
if (this.limitPerFolder && thisDay == this.cols) {
|
||||
while (i + 1 < photos.length) {
|
||||
photo = photos[i + 1];
|
||||
if (datetime != (photo.taken || photo.modified || photo.added).replace(/T.*$/, "")) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
thisDay = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_loadPhotos: function(start, dir, append) {
|
||||
@ -341,7 +439,7 @@
|
||||
var params = {
|
||||
limit: Math.ceil(this.clientWidth / 200) * Math.ceil(this.clientHeight / 200),
|
||||
dir: dir
|
||||
}, url = "";
|
||||
}, query = "";
|
||||
if (start) {
|
||||
params.next = start;
|
||||
}
|
||||
@ -349,15 +447,15 @@
|
||||
params.sort = this.sortOrder;
|
||||
}
|
||||
for (var key in params) {
|
||||
if (url == "") {
|
||||
url = "?";
|
||||
if (query == "") {
|
||||
query = "?";
|
||||
} else {
|
||||
url += "&";
|
||||
query += "&";
|
||||
}
|
||||
url += key + "=" + encodeURIComponent(params[key]);
|
||||
query += key + "=" + encodeURIComponent(params[key]);
|
||||
}
|
||||
|
||||
window.fetch("api/v1/photos" + url, function(error, xhr) {
|
||||
window.fetch("api/v1/photos" + (this.path || "") + query, function(error, xhr) {
|
||||
this.loading = false;
|
||||
if (error) {
|
||||
console.error(JSON.stringify(error, null, 2));
|
||||
@ -391,7 +489,7 @@
|
||||
this.photos = results.items;
|
||||
}
|
||||
|
||||
if (dir == +1) {
|
||||
if (dir == -1) {
|
||||
this.prev = start ? true : false;
|
||||
this.next = results.more ? true : false;
|
||||
} else {
|
||||
|
@ -64,6 +64,7 @@ app.use(function(req, res, next){
|
||||
|
||||
app.use(basePath + "api/v1/photos", require("./routes/photos"));
|
||||
app.use(basePath + "api/v1/days", require("./routes/days"));
|
||||
app.use(basePath + "api/v1/albums", require("./routes/albums"));
|
||||
|
||||
/* Declare the "catch all" index route last; the final route is a 404 dynamic router */
|
||||
app.use(basePath, require("./routes/index"));
|
||||
|
71
server/routes/albums.js
Normal file
71
server/routes/albums.js
Normal file
@ -0,0 +1,71 @@
|
||||
"use strict";
|
||||
|
||||
const express = require("express"),
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
config = require("config"),
|
||||
moment = require("moment");
|
||||
|
||||
let photoDB;
|
||||
|
||||
require("../db").then(function(db) {
|
||||
photoDB = db;
|
||||
});
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/*", function(req, res/*, next*/) {
|
||||
let url = decodeURI(req.url).replace(/\?.*$/, ""),
|
||||
query = "SELECT * FROM albums WHERE path=:path";
|
||||
|
||||
if (url == "/") {
|
||||
url = "";
|
||||
}
|
||||
|
||||
return photoDB.sequelize.query(query, {
|
||||
replacements: {
|
||||
path: url
|
||||
},
|
||||
type: photoDB.Sequelize.QueryTypes.SELECT
|
||||
}).then(function(parent) {
|
||||
if (parent.length == 0) {
|
||||
return res.status(404).send(req.url + " not found");
|
||||
}
|
||||
|
||||
parent = parent[0];
|
||||
for (var key in parent) {
|
||||
if (parent[key] instanceof Date) {
|
||||
parent[key].setHours(0, 0, 0, 0);
|
||||
parent[key] = moment(parent[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return photoDB.sequelize.query("SELECT * FROM albums WHERE parentId=:parentId", {
|
||||
replacements: {
|
||||
parentId: parent.id
|
||||
},
|
||||
type: photoDB.Sequelize.QueryTypes.SELECT
|
||||
}).then(function(children) {
|
||||
children.forEach(function(album) {
|
||||
for (var key in album) {
|
||||
if (album[key] instanceof Date) {
|
||||
album[key].setHours(0, 0, 0, 0);
|
||||
album[key] = moment(album[key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let results = {
|
||||
album: parent,
|
||||
children: children
|
||||
};
|
||||
return res.status(200).json(results);
|
||||
});
|
||||
}).catch(function(error) {
|
||||
|
||||
console.error("Query failed: " + query);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -24,7 +24,7 @@ const router = express.Router();
|
||||
|
||||
*/
|
||||
|
||||
router.get("/", function(req, res/*, next*/) {
|
||||
router.get("/*", function(req, res/*, next*/) {
|
||||
let limit = parseInt(req.query.limit) || 50,
|
||||
order = (parseInt(req.query.dir) == -1) ? "DESC" : "", id, cursor, index;
|
||||
|
||||
@ -59,7 +59,7 @@ router.get("/", function(req, res/*, next*/) {
|
||||
return photoDB.sequelize.query(query, {
|
||||
replacements: {
|
||||
cursor: cursor,
|
||||
path: req.url.replace(/\?.*$/, "") + "%"
|
||||
path: decodeURI(req.url).replace(/\?.*$/, "") + "%"
|
||||
},
|
||||
type: photoDB.Sequelize.QueryTypes.SELECT
|
||||
}).then(function(photos) {
|
||||
@ -100,8 +100,8 @@ router.get("/", function(req, res/*, next*/) {
|
||||
photos.slice(limit, photos.length);
|
||||
}
|
||||
photos.forEach(function(photo) {
|
||||
photo.path = encodeURI(photo.path);
|
||||
photo.filename = encodeURI(photo.filename);
|
||||
// photo.path = encodeURI(photo.path);
|
||||
// photo.filename = encodeURI(photo.filename);
|
||||
});
|
||||
|
||||
let results = {
|
||||
|
@ -17,7 +17,7 @@ function scanDir(parent, path) {
|
||||
let extensions = [ "jpg", "jpeg", "png", "gif", "nef" ],
|
||||
re = new RegExp("\.((" + extensions.join(")|(") + "))$", "i"),
|
||||
replacements = {
|
||||
path: path,
|
||||
path: path.slice(picturesPath.length),
|
||||
parent: parent || null
|
||||
};
|
||||
|
||||
@ -35,10 +35,7 @@ function scanDir(parent, path) {
|
||||
if (results.length == 0) {
|
||||
// console.log("Adding " + path + " under " + parent, replacements);
|
||||
return photoDB.sequelize.query("INSERT INTO albums (path,parentId) VALUES(:path,:parent)", {
|
||||
replacements: {
|
||||
path: path,
|
||||
parent: parent || null
|
||||
},
|
||||
replacements: replacements
|
||||
}).then(function(results) {
|
||||
return results[1].lastID;
|
||||
});
|
||||
@ -47,7 +44,7 @@ function scanDir(parent, path) {
|
||||
}
|
||||
}).then(function(parent) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
console.log("Scanning path " + path + " under parent " + parent);
|
||||
console.log("Scanning " + replacements.path);
|
||||
|
||||
fs.readdir(path, function(err, files) {
|
||||
if (err) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user