Added lightbox

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2018-08-22 18:31:55 -07:00
parent 7330f5eb5d
commit 5f9fbe21fb
7 changed files with 386 additions and 29 deletions

View File

@ -47,7 +47,8 @@
"iron-location": "PolymerElements/iron-location#^1.0.0",
"iron-a11y-keys": "PolymerElements/iron-a11y-keys#^1.0.0",
"iron-a11y-keys-behavior": "PolymerElements/iron-a11y-keys-behavior#^1.0.0",
"es6-shim": "^0.35.3"
"es6-shim": "^0.35.3",
"paper-radio-group": "PolymerElements/paper-radio-group#^2.2.0"
},
"resolutions": {
"polymer": "^1.4.0",

View File

@ -0,0 +1,193 @@
<!doctype html>
<html>
<head>
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/iron-icon/iron-icon.html">
<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
<link rel="import" href="../bower_components/iron-pages/iron-pages.html">
</head>
<dom-module id="photo-lightbox">
<template>
<style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning">
:host {
display: none;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
transition: opacity 0.5s ease-in-out;
-webkit-transition: opacity 0.5s ease-in-out;
border-width: 5px;
box-sizing: border-box;
pointer-events: all;
}
#image {
display: inline-block;
position: relative;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
transition: opacity 0.5s ease-in-out;
-webkit-transition: opacity 0.5s ease-in-out;
pointer-events: none;
}
</style>
<div id="image"></div>
</template>
<script>
"use strict";
Polymer({
is: "photo-lightbox",
properties: {
"src": {
type: String
},
"thumb": {
type: String
}
},
observers: [
"srcChanged(src)"
],
listeners: {
"keydown": "onKeyDown",
"tap": "onTap",
"blur": "onBlur",
"focus": "onFocus",
"scroll": "onScroll"
},
onFocus: function() {
this.hasFocus = true;
},
onBlur: function() {
this.hasFocus = false;
},
next: function() {
this.fire("next");
},
previous: function() {
this.fire("previous");
},
onKeyDown: function(e) {
if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) {
return;
}
switch (e.keyCode) {
case 39: /* right */
if (this.hasFocus) {
this.next();
}
break;
case 37: /* left */
if (this.hasFocus) {
this.previous();
}
break;
case 27: /* escape */
this.hasFocus = false;
this.close();
break;
default:
console.log(e.keyCode);
break;
}
if (this.hasFocus) {
e.preventDefault();
e.stopPropagation();
}
},
close: function() {
this.style.opacity = 0;
this.async(function() {
this.style.display = 'none';
this.image = undefined;
this.$.image.style.opacity = 0;
this.$.image.style.removeProperty('background-image');
this.closed = true;
this.opened = false;
this.fire("close");
}, 250);
},
srcChanged: function(src) {
this.loadImage(src);
},
onTap: function(event) {
if (!this.style.display || this.style.display == "none") {
return;
}
if (event.detail.x <= 0.1 * this.offsetWidth) {
this.previous();
} else if (event.detail.x >= 0.9 * this.offsetWidth) {
this.next();
} else {
this.close();
}
},
open: function() {
this.closed = false;
this.opened = true;
this.style.opacity = 1;
this.style.borderColor = 'blue';
this.style.display = 'block';
this.focus();
},
loadImage: function(image) {
if (this.$.image.style.opacity != 0) {
this.waitUntil = (Date.now() / 1000) + 500;
} else {
this.waitUntil = 0;
}
this.$.image.style.opacity = 0;
this.style.borderColor = 'orange';
this.image = new Image();
this.loading = image;
this.image.onload = function(src) {
var remaining = Math.max(this.waitUntil - Date.now() / 1000, 0);
this.async(function() {
if (!this.image || this.loading != src) {
return;
}
this.$.image.style.backgroundImage = 'url("' + this.image.src + '")';
this.$.image.style.opacity = 1;
this.style.borderColor = 'black';
this.image = undefined;
}, remaining);
}.bind(this, image);
this.image.src = image;
},
attached: function() {
}
});
</script>
</dom-module>
</html>

View File

@ -17,11 +17,14 @@
<link rel="import" href="../../bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html">
<link rel="import" href="../../bower_components/paper-input/paper-input.html">
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
<link rel="import" href="../../bower_components/paper-radio-group/paper-radio-group.html">
<link rel="import" href="../../bower_components/paper-radio-button/paper-radio-button.html">
<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
<link rel="import" href="../../bower_components/iron-resizable-behavior/iron-resizable-behavior.html">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" />
<link rel="import" href="../../elements/photo-lightbox.html">
<link rel="import" href="../../elements/photo-thumbnail.html">
<script src="fetch.js"></script>
@ -68,17 +71,35 @@
left: -1000px;
}
app-header {
background-color: yellow;
}
app-header-layout {
--layout-fit: {
overflow-y: hidden !important;
};
}
.date-line {
display: block;
padding: 0.5em 0;
width: 100%;
}
#content {
margin: 1em;
}
photo-thumbnail {
border: 3px solid rgba(0, 0, 0, 0);
}
photo-thumbnail[selected] {
border-color: blue;
}
</style>
<app-location route="{{route}}"></app-location>
@ -86,13 +107,16 @@
<photo-thumbnail id="placeholder"></photo-thumbnail>
<app-header-layout fullbleed has-scrolling-region>
<app-header slot="header" fixed>
<app-header slot="header">
<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>
<div>[[path]]</div>
</app-header>
<div id="content">
<div id="thumbnails" class="thumbnails layout horizontal wrap">
</div>
<div id="thumbnails" class="thumbnails layout horizontal wrap"></div>
<div id="magic"></div>
<div class="layout horizontal">
<paper-button disabled$="[[!prev]]" on-tap="loadPrevPhotos">prev</paper-button>
@ -106,6 +130,7 @@
</div>
</app-header-layout>
<paper-toast id="toast"></paper-toast>
<photo-lightbox tabindex="0" id="lightbox" on-close="lightBoxClose" on-next="lightBoxNext" on-previous="lightBoxPrevious"></photo-lightbox>
</template>
<script>
@ -114,6 +139,10 @@
Polymer({
is: "ketr-photos",
properties: {
order: {
type: String,
value: "by-date"
},
"loading": Boolean,
"photos": Array,
prev: {
@ -130,6 +159,65 @@
"widthChanged(calcWidth)"
],
listeners: {
"scroll": "onScroll",
"iron-resize" : "onResize"
},
onScroll: function(event) {
if (this.disableScrolling) {
event.preventDefault();
window.scrollTo(this.topStickX, this.topStickY);
}
},
findPhoto: function(photo) {
var photos = this.$.thumbnails.querySelectorAll("photo-thumbnail");
for (var i = 0; i < photos.length; i++) {
if (photos[i] == photo) {
return { index: i, photos: photos };
}
}
return { index: -1, photos: photos };
},
lightBoxClose: function(event) {
this.disableScrolling = false;
},
lightBoxNext: function(event) {
var results = this.findPhoto(this.lightBoxElement);
/* If there are less than 2 rows less (2 * cols) then queue up more to load! */
if (results.index + (this.cols * 2) >= results.photos.length && this.next) {
this.loadNextPhotos();
}
if (results.index == -1 || results.index + 1 >= results.photos.length) {
return;
}
var photo = results.photos[results.index + 1];
photo.scrollIntoView(false);
this.topStickX = window.scrollX;
this.topStickY = window.scrollY;
this.loadLightbox(photo);
},
lightBoxPrevious: function(event) {
var results = this.findPhoto(this.lightBoxElement);
if (results.index == -1 || results.index < 1) {
return;
}
var photo = results.photos[results.index - 1];
photo.scrollIntoView(false);
this.topStickX = window.scrollX;
this.topStickY = window.scrollY;
this.loadLightbox(photo);
},
widthChanged: function(calcWidth) {
var thumbs = this.$.thumbnails.querySelectorAll("photo-thumbnail");
Array.prototype.forEach.call(thumbs, function(thumb) {
@ -142,17 +230,27 @@
Polymer.IronResizableBehavior
],
listeners: {
"iron-resize" : "onResize"
},
date: function(item) {
var datetime = item.taken || item.modified || item.added;
return datetime.replace(/T.*$/, "");
},
loadLightbox: function(el) {
if (this.lightBoxElement) {
this.lightBoxElement.removeAttribute("selected");
}
el.setAttribute("selected", true);
this.$.lightbox.src = this.base + el.item.path + "/" + el.item.filename;
this.lightBoxElement = el;
this.disableScrolling = true;
this.topStickX = window.scrollX;
this.topStickY = window.scrollY;
this.$.lightbox.open();
},
_imageTap: function(event) {
window.open(this.base + event.model.item.path + "/" + event.model.item.filename, "image");
this.loadLightbox(event.currentTarget);
},
_pathTap: function(event) {
@ -161,12 +259,12 @@
loadNextPhotos: function() {
var cursor = this.photos[this.photos.length - 1];
this._loadPhotos(cursor.id + "_" + cursor.taken.toString().replace(/T.*/, ""), +1, true);
this._loadPhotos(cursor.taken.toString().replace(/T.*/, "") + "_" + cursor.id, +1, true);
},
loadPrevPhotos: function() {
var cursor = this.photos[0];
this._loadPhotos(cursor.id + "_" + cursor.taken.toString().replace(/T.*/, ""), -1);
this._loadPhotos(cursor.taken.toString().replace(/T.*/, "") + "_" + cursor.id, -1);
},
_loadPhotos: function(start, dir, append) {
@ -226,13 +324,14 @@
thumbnail.item = photo;
thumbnail.width = this.calcWidth;
thumbnail.addEventListener("click", this._imageTap.bind(this));
var datetime = (photo.taken || photo.modified || photo.added).replace(/T.*$/, "");
if (this.lastDate != datetime) {
var dateLine = document.createElement("div");
dateLine.classList.add("date-line");
dateLine.textContent = datetime;
this.lastDate = datetime;
Polymer.dom(this.$.thumbnails).appendChild(dateLine);
var datetime = (photo.taken || photo.modified || photo.added).replace(/T.*$/, ""),
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);
}
Polymer.dom(this.$.thumbnails).appendChild(thumbnail);
@ -257,9 +356,11 @@
onResize: function(event) {
this.debounce("resize", function() {
var width = Math.max(this.$.placeholder.offsetWidth || 0, 200),
cols = Math.floor(this.clientWidth / width),
calc = width + Math.floor((this.clientWidth % width) / cols);
var width = Math.max(this.$.placeholder.offsetWidth || 0, 200);
this.cols = Math.floor(this.clientWidth / width);
var calc = width + Math.floor((this.clientWidth % width) / this.cols);
if (calc != this.calcWidth) {
this.calcWidth = calc;
}
@ -303,6 +404,8 @@
this._loadPhotos();
this.onResize();
document.addEventListener("scroll", this.onScroll.bind(this));
}
});
});

View File

@ -63,6 +63,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"));
/* Declare the "catch all" index route last; the final route is a 404 dynamic router */
app.use(basePath, require("./routes/index"));

49
server/routes/days.js Normal file
View File

@ -0,0 +1,49 @@
"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();
/* Each photos has:
* locations
* people
* date
* tags
* photo info
*/
router.get("/", function(req, res/*, next*/) {
let start = moment(req.query.start || null) || null,
end = moment(req.query.end || null) || null;
let query = "SELECT DATE(taken) AS date,COUNT(*) AS count FROM photos WHERE path LIKE :path GROUP BY DATE(taken) ORDER BY date";
return photoDB.sequelize.query(query, {
replacements: {
path: req.url.replace(/\?.*$/, "") + "%"
},
type: photoDB.Sequelize.QueryTypes.SELECT
}).then(function(days) {
let results = {
items: days
};
return res.status(200).json(results);
}).catch(function(error) {
console.error("Query failed: " + query);
return Promise.reject(error);
});
});
module.exports = router;

View File

@ -30,8 +30,8 @@ router.get("/", function(req, res/*, next*/) {
if (req.query.next) {
let parts = req.query.next.split("_");
cursor = parts[1];
id = parseInt(parts[0]);
cursor = parts[0];
id = parseInt(parts[1]);
} else {
cursor = "";
id = -1;

View File

@ -139,8 +139,9 @@ const sharp = require("sharp"), exif = require("exif-reader");
function triggerWatcher() {
setTimeout(triggerWatcher, 1000);
if (!processRunning && processQueue.length) {
let lastMessage = moment(), toProcess = processQueue.length;
processRunning = true;
return Promise.map(processQueue, function(entry) {
var path = entry[0], file = entry[1], created = entry[2], albumId = entry[3],
@ -172,20 +173,23 @@ function triggerWatcher() {
if (metadata.exif && metadata.exif.exif && metadata.exif.exif.DateTimeOriginal && !isNaN(metadata.exif.exif.DateTimeOriginal.valueOf())) {
metadata.exif.exif.DateTimeOriginal.setHours(0, 0, 0, 0);
metadata.exif.exif.DateTimeOriginal = metadata.exif.exif.DateTimeOriginal.toISOString().replace(/T.*/, "");
// console.log(metadata.exif.exif.DateTimeOriginal);
replacements.taken = moment(metadata.exif.exif.DateTimeOriginal, "YYYY-MM-DD").format().replace(/T.*/, "");
replacements.modified = moment(metadata.exif.exif.DateTimeOriginal).format().replace(/T.*/, "");
if (replacements.taken == "Invalid date") {
console.log("Invalid EXIF date information: ", JSON.stringify(metadata.exif.exif));
replacements.taken = replacements.modified = replacements.added;
}
} else {
// console.log("Missing EXIF info for: " + file);
//console.log(JSON.stringify(metadata.exif, null, 2));
let patterns = /(20[0-9][0-9]-?[0-9][0-9]-?[0-9][0-9])[_\-]?([0-9]*)/, date = replacements.added;
let match = file.match(patterns);
if (match) {
date = moment(match[1].replace(/-/g, ""), "YYYYMMDD").format();
// console.log("Constructed date: " + date);
if (date == "Invalid date") {
date = moment(created).format();
}
} else {
date = moment(created).format();
// console.log("Date from file: ", src, date);
}
replacements.taken = replacements.modified = date;
}
@ -194,6 +198,12 @@ function triggerWatcher() {
return photoDB.sequelize.query("INSERT INTO photos " +
"SET albumId=:albumId,path=:path,filename=:filename,added=DATE(:added),modified=DATE(:modified),taken=DATE(:taken),width=:width,height=:height", {
replacements: replacements
}).then(function() {
toProcess--;
if (moment().add(-5, 'seconds') > lastMessage) {
console.log("Items to be processed: " + toProcess);
lastMessage = moment();
}
});
}).catch(function(error) {
console.log("Error resizing or writing " + src, error);