Added albums API

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2018-08-27 15:17:39 -07:00
parent 29dafae886
commit bfd872f60a
6 changed files with 228 additions and 48 deletions

View File

@ -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() {

View File

@ -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 {

View File

@ -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
View 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;

View File

@ -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 = {

View File

@ -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) {