784 lines
25 KiB
HTML
Executable File
784 lines
25 KiB
HTML
Executable File
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
|
|
|
<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html">
|
|
<link rel="import" href="../../bower_components/app-layout/app-drawer-layout/app-drawer-layout.html">
|
|
<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html">
|
|
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
|
<link rel="import" href="../../bower_components/app-layout/app-drawer/app-drawer.html">
|
|
<link rel="import" href="../../bower_components/app-route/app-location.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">
|
|
<link rel="import" href="../../bower_components/iron-resizable-behavior/iron-resizable-behavior.html">
|
|
|
|
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
|
|
<link rel="import" href="../../bower_components/paper-checkbox/paper-checkbox.html">
|
|
<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html">
|
|
<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-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-spinner/paper-spinner.html">
|
|
<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
|
|
<script src="../../bower_components/moment/moment.js"></script>
|
|
|
|
<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>
|
|
|
|
<style>
|
|
body,* {
|
|
font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
|
|
}
|
|
|
|
b,strong {
|
|
font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
|
|
}
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<dom-module id="ketr-photos">
|
|
<template>
|
|
<style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning">
|
|
:host {
|
|
}
|
|
|
|
#header {
|
|
padding: 0.5em 1em;
|
|
background: #ddd;
|
|
box-shadow: 0px 0px 5px black;
|
|
}
|
|
#breadcrumb {
|
|
padding: 0.5em;
|
|
}
|
|
|
|
#albums {
|
|
height: 100%;
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
#albums > div {
|
|
margin: 0.5em;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#breadcrumb > div {
|
|
margin-right: 0.5em;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#albums div:hover,
|
|
#breadcrumb > div:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
app-toolbar {
|
|
background-color: rgba(64, 0, 64, 0.5);
|
|
color: white;
|
|
}
|
|
|
|
#toast[error] {
|
|
--paper-toast-background-color: red;
|
|
--paper-toast-color: white;
|
|
}
|
|
|
|
#placeholder {
|
|
position: absolute;
|
|
left: -1000px;
|
|
}
|
|
|
|
app-header {
|
|
background-color: yellow;
|
|
}
|
|
|
|
/* app-header-layout {
|
|
--layout-fit: {
|
|
overflow-y: hidden !important;
|
|
};
|
|
}
|
|
*/
|
|
|
|
.date-line {
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
padding: 0.5em 0;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.date-line .album-line {
|
|
cursor: pointer;
|
|
color: #ddd;
|
|
}
|
|
|
|
.album-line > div:not(:last-child) {
|
|
margin-right: 0.5em;
|
|
}
|
|
|
|
.album-line > div:first-child {
|
|
margin-left: 0.5em;
|
|
}
|
|
|
|
.album-line > div:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
photo-thumbnail {
|
|
--photo-thumbnail: {
|
|
border: 3px solid white;
|
|
};
|
|
}
|
|
|
|
photo-thumbnail[selected] {
|
|
--photo-thumbnail: {
|
|
border-color: blue;
|
|
};
|
|
}
|
|
|
|
</style>
|
|
|
|
<app-location route="{{route}}"></app-location>
|
|
|
|
<app-drawer-layout id="albumLayout">
|
|
<app-drawer persistent id="albumList" slot="drawer">
|
|
<div id="albums" class="layout vertical">
|
|
<template is="dom-repeat" items="[[breadcrumb(path)]]">
|
|
<div on-tap="loadPath">[[item.name]] /</div>
|
|
</template>
|
|
<template is="dom-repeat" items="[[albums.children]]">
|
|
<div on-tap="loadPath">[[item.name]]</div>
|
|
</template>
|
|
</div>
|
|
</app-drawer>
|
|
<app-header-layout>
|
|
<app-header reveals slot="header">
|
|
<div id="header" class="layout horizontal center justified">
|
|
<div id="breadcrumb" class="horizontal layout center">
|
|
<template is="dom-repeat" items="[[breadcrumb(path)]]">
|
|
<div on-tap="loadPath">[[item.name]] /</div>
|
|
</template>
|
|
</div>
|
|
<paper-spinner active$="[[loading]]" class="thin"></paper-spinner>
|
|
</div>
|
|
</app-header>
|
|
<div id="content" fullbleed class="flex layout vertical">
|
|
<div id="thumbnails" class="layout horizontal wrap"></div>
|
|
<div id="magic"></div>
|
|
<div class="layout horizontal">
|
|
<paper-button disabled$="[[!next]]" on-tap="loadNextPhotos">next</paper-button>
|
|
</div>
|
|
</div>
|
|
</app-header-layout>
|
|
</app-drawer-layout>
|
|
<paper-toast id="toast"></paper-toast>
|
|
<photo-lightbox tabindex="0" id="lightbox" on-close="lightBoxClose" on-next="lightBoxNext" on-previous="lightBoxPrevious"></photo-lightbox>
|
|
<photo-thumbnail id="placeholder"></photo-thumbnail>
|
|
</template>
|
|
|
|
<script>
|
|
document.addEventListener("WebComponentsReady", function() {
|
|
"use strict";
|
|
Polymer({
|
|
is: "ketr-photos",
|
|
properties: {
|
|
order: {
|
|
type: String,
|
|
value: "by-date"
|
|
},
|
|
"loading": Boolean,
|
|
pendingPhotos: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
next: {
|
|
type: Boolean,
|
|
value: false
|
|
},
|
|
breakOnDayChange: {
|
|
type: Boolean,
|
|
value: true,
|
|
reflectToAttribute: true
|
|
},
|
|
showAlbums: {
|
|
type: Boolean,
|
|
computed: "shouldShowAlbums(order)"
|
|
},
|
|
path: {
|
|
type: String,
|
|
value: ""
|
|
},
|
|
visibleThumbs: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
thumbnails: {
|
|
type: Array,
|
|
value: []
|
|
}
|
|
},
|
|
|
|
shouldShowAlbums: function(order) {
|
|
return order == "by-album";
|
|
},
|
|
|
|
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)",
|
|
"orderChanged(order)"
|
|
],
|
|
|
|
orderChanged: function(order) {
|
|
console.log("Order: " + order);
|
|
if (order == "by-album") {
|
|
this.$.albumLayout.forceNarrow = false;
|
|
this.$.albumList.open();
|
|
this.$.albumList.persistent = true;
|
|
} else if (order == "by-date") {
|
|
this.$.albumList.close();
|
|
this.$.albumLayout.forceNarrow = true;
|
|
this.$.albumList.resetLayout();
|
|
}
|
|
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
|
},
|
|
|
|
listeners: {
|
|
"scroll": "onScroll",
|
|
"iron-resize" : "onResize"
|
|
},
|
|
|
|
loadPath: function(event) {
|
|
this.path = event.model.item.path;
|
|
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
|
this.next = false;
|
|
this._loadAlbums();
|
|
this._loadPhotos();
|
|
},
|
|
|
|
loadAlbum: function(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
this.path = event.detail;
|
|
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
|
this.next = false;
|
|
this._loadAlbums();
|
|
this._loadPhotos();
|
|
},
|
|
|
|
|
|
onScroll: function(event) {
|
|
if (this.disableScrolling) {
|
|
event.preventDefault();
|
|
window.scrollTo(this.topStickX, this.topStickY);
|
|
return;
|
|
}
|
|
|
|
this.triggerVisibilityChecks();
|
|
},
|
|
|
|
checkPosition: function(thumbIndex) {
|
|
var rect = this.thumbnails[thumbIndex].getBoundingClientRect();
|
|
|
|
if (rect.top > window.innerHeight) {
|
|
return +1;
|
|
}
|
|
|
|
if (rect.top + rect.height < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
},
|
|
|
|
triggerVisibilityChecks: function() {
|
|
this.debounce("hide-or-show", function() {
|
|
if (!this.thumbnails || !this.thumbnails.length) {
|
|
return;
|
|
}
|
|
|
|
var index, start, stop, length = this.thumbnails.length;
|
|
|
|
start = 0;
|
|
stop = this.thumbnails.length - 1;
|
|
/* If there were visible thumbs, use the one from the middle as the starting
|
|
* point to check visibility. Otherwise, binary-tree the entire image list */
|
|
if (this.visibleThumbs.length) {
|
|
index = this.visibleThumbs[this.visibleThumbs.length >> 1];
|
|
} else {
|
|
index = length >> 1;
|
|
}
|
|
|
|
var pos = this.checkPosition(index), last = -1;
|
|
while (pos != 0 && last != index) {
|
|
last = index; /* safety escape in case the DOM changed and nothing matches */
|
|
|
|
if (pos == +1) { /* Checked item was too far down page, so look closer to start */
|
|
stop = index - 1;
|
|
if (stop < 0) {
|
|
console.log("No items in viewport -- all are below");
|
|
return;
|
|
}
|
|
index = start + ((stop - start) >> 1);
|
|
} else { /* Checked item was too high up on the page, so look farther */
|
|
start = index + 1;
|
|
if (start == length) {
|
|
console.log("No items in viewport -- all are above");
|
|
return;
|
|
}
|
|
index += (stop - start) >> 1;
|
|
}
|
|
|
|
pos = this.checkPosition(index);
|
|
}
|
|
|
|
if (pos != 0) {
|
|
console.log("DOM changed or viewport changed and search would never exit; re-scheduling check")
|
|
//this.triggerVisibilityChecks();
|
|
return;
|
|
}
|
|
|
|
/* 'index' now points to a thumbnail that is in the viewport; scan
|
|
* above and below until a non-in-viewport item is found in each
|
|
* direction. That creates the new 'visible' array. */
|
|
var visible = [];
|
|
visible.push(index);
|
|
/* Scan above... */
|
|
var tmp = index - 1;
|
|
while (tmp >= 0 && this.checkPosition(tmp) == 0) {
|
|
visible.push(tmp--);
|
|
}
|
|
tmp = index + 1;
|
|
while (tmp < length && this.checkPosition(tmp) == 0) {
|
|
visible.push(tmp++);
|
|
}
|
|
|
|
visible.sort(function(a, b) {
|
|
return a - b;
|
|
});
|
|
|
|
var i;
|
|
/* Remove 'visible' attribute from any thumbnail
|
|
* that was visible and is not in the new set of
|
|
* visible thumbs. */
|
|
this.visibleThumbs.forEach(function(index) {
|
|
for (i = 0; i < visible.length; i++) {
|
|
if (visible[i] == index) {
|
|
return;
|
|
}
|
|
}
|
|
this.thumbnails[index].visible = false;
|
|
}.bind(this));
|
|
|
|
/* Randomly index the visible array, keeping the center
|
|
* in the middle. This makes the loading look more organic. */
|
|
for (var i = 0, j = visible.length - 1; i < j; i++, j--) {
|
|
if (Math.random() > 0.5) {
|
|
var tmp = visible[i];
|
|
visible[i] = visible[j];
|
|
visible[j] = tmp;
|
|
}
|
|
}
|
|
|
|
/* Turn on visibility for any item that is now visible */
|
|
visible.forEach(function(index) {
|
|
if (this.thumbnails[index].visible != true) {
|
|
this.thumbnails[index].visible = true;
|
|
}
|
|
}.bind(this));
|
|
|
|
this.visibleThumbs = visible;
|
|
});
|
|
},
|
|
|
|
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);
|
|
this.triggerVisibilityChecks();
|
|
},
|
|
|
|
|
|
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);
|
|
this.triggerVisibilityChecks();
|
|
},
|
|
|
|
widthChanged: function(calcWidth) {
|
|
/*
|
|
var thumbs = this.$.thumbnails.querySelectorAll("photo-thumbnail");
|
|
Array.prototype.forEach.call(thumbs, function(thumb) {
|
|
thumb.width = calcWidth;
|
|
});
|
|
*/
|
|
},
|
|
|
|
behaviors: [
|
|
/* @polymerBehavior Polymer.IronResizableBehavior */
|
|
Polymer.IronResizableBehavior
|
|
],
|
|
|
|
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) {
|
|
this.loadLightbox(event.currentTarget);
|
|
},
|
|
|
|
_pathTap: function(event) {
|
|
window.location.href = event.model.item.filepath;
|
|
},
|
|
|
|
loadNextPhotos: function() {
|
|
if (!this.cursor) {
|
|
return;
|
|
}
|
|
this._loadPhotos(this.cursor.taken.toString().replace(/T.*/, "") + "_" + this.cursor.id, -1, true);
|
|
},
|
|
|
|
appendItems: function(photos) {
|
|
if (!this.pendingPhotos || !photos || photos.length == 0) {
|
|
return;
|
|
}
|
|
|
|
this.pendingPhotos = this.pendingPhotos.concat(photos);
|
|
|
|
this.async(this.processItems.bind(this));
|
|
|
|
console.log("Total pending: " + this.pendingPhotos.length);
|
|
},
|
|
|
|
processItems: function() {
|
|
if (this.pendingPhotos.length == 0) {
|
|
return;
|
|
}
|
|
|
|
var lastPath = null;
|
|
var albums = this.querySelectorAll(".album-line");
|
|
if (albums.length) {
|
|
lastPath = albums[albums.length - 1];
|
|
}
|
|
|
|
var photos = this.pendingPhotos.splice(0, 50);
|
|
|
|
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("load-image", this._imageTap.bind(this));
|
|
thumbnail.addEventListener("load-album", this.loadAlbum.bind(this));
|
|
datetime = (photo.taken || photo.modified || photo.added).replace(/T.*$/, "");
|
|
|
|
var dateBlock = this.querySelector("#date-" + datetime), thumbnails;
|
|
if (!dateBlock) {
|
|
dateBlock = document.createElement("div");
|
|
dateBlock.id = "date-" + datetime;
|
|
dateBlock.classList.add("date-line");
|
|
dateBlock.classList.add("layout");
|
|
dateBlock.classList.add("vertical");
|
|
var header = document.createElement("div");
|
|
header.classList.add("header");
|
|
header.classList.add("layout");
|
|
header.classList.add("center");
|
|
header.classList.add("horizontal");
|
|
var div = document.createElement("div");
|
|
div.classList.add("date");
|
|
div.textContent = window.moment(datetime, "YYYY-MM-DD").calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, "");
|
|
Polymer.dom(dateBlock).appendChild(header);
|
|
Polymer.dom(header).appendChild(div);
|
|
thumbnails = document.createElement("div");
|
|
thumbnails.classList.add("thumbnails");
|
|
thumbnails.classList.add("layout");
|
|
thumbnails.classList.add("horizontal");
|
|
thumbnails.classList.add("wrap");
|
|
Polymer.dom(dateBlock).appendChild(thumbnails);
|
|
Polymer.dom(this.$.thumbnails).appendChild(dateBlock);
|
|
} else {
|
|
thumbnails = Polymer.dom(dateBlock).querySelector(".thumbnails");
|
|
}
|
|
|
|
if (this.order == "by-album") {
|
|
if (lastPath != photo.path) {
|
|
lastPath = photo.path;
|
|
var albumBlock = document.createElement("div");
|
|
albumBlock.classList.add("album-line");
|
|
albumBlock.classList.add("layout");
|
|
albumBlock.classList.add("horizontal");
|
|
var trail = this.breadcrumb(lastPath);
|
|
trail.forEach(function(crumb) {
|
|
var div = document.createElement("div");
|
|
div.path = crumb.path;
|
|
div.textContent = crumb.name + " /";
|
|
div.addEventListener("tap", this.pathTapped.bind(this));
|
|
albumBlock.appendChild(div);
|
|
}.bind(this));
|
|
|
|
var header = dateBlock.querySelector(".header");
|
|
Polymer.dom(header).appendChild(albumBlock);
|
|
}
|
|
}
|
|
|
|
Polymer.dom(thumbnails).appendChild(thumbnail);
|
|
this.thumbnails.push(thumbnail);
|
|
}
|
|
|
|
if (this.pendingPhotos.length) {
|
|
this.async(this.processItems.bind(this));
|
|
} else {
|
|
if (this.next && this.cursor) {
|
|
this.async(function() {
|
|
this._loadPhotos(this.cursor.taken.toString().replace(/T.*/, "") + "_" + this.cursor.id, -1, true, this.limit * 2);
|
|
}, 250);
|
|
} else {
|
|
console.log("All photos are loaded: " + this.thumbnails.length);
|
|
}
|
|
this.triggerVisibilityChecks();
|
|
}
|
|
},
|
|
|
|
pathTapped: function(event) {
|
|
this.path = event.currentTarget.path;
|
|
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
|
this.next = false;
|
|
this._loadAlbums();
|
|
this._loadPhotos();
|
|
},
|
|
|
|
_loadPhotos: function(start, dir, append, limit) {
|
|
if (this.loading == true) {
|
|
return;
|
|
}
|
|
this.loading = true;
|
|
|
|
this.limit = limit || 500;
|
|
|
|
dir = dir || -1;
|
|
var params = {
|
|
limit: this.limit,
|
|
dir: dir
|
|
}, query = "";
|
|
if (start) {
|
|
params.next = start;
|
|
}
|
|
if (this.sortOrder) {
|
|
params.sort = this.sortOrder;
|
|
}
|
|
for (var key in params) {
|
|
if (query == "") {
|
|
query = "?";
|
|
} else {
|
|
query += "&";
|
|
}
|
|
query += key + "=" + encodeURIComponent(params[key]);
|
|
}
|
|
|
|
console.log("Requesting " + this.limit + " photos.");
|
|
|
|
window.fetch("api/v1/photos" + (this.path || "") + query, function(error, xhr) {
|
|
this.loading = false;
|
|
if (error) {
|
|
console.error(JSON.stringify(error, null, 2));
|
|
return;
|
|
}
|
|
|
|
var results;
|
|
try {
|
|
results = JSON.parse(xhr.responseText);
|
|
} catch (___) {
|
|
this.$.toast.text = "Unable to load/parse photo list.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse photos");
|
|
return;
|
|
}
|
|
|
|
var base = document.querySelector("base");
|
|
if (base) {
|
|
this.base = new URL(base.href).pathname.replace(/\/$/, ""); /* Remove trailing slash if there */
|
|
} else {
|
|
this.base = "";
|
|
}
|
|
|
|
console.log(results.items.length + " photos received.");
|
|
|
|
this.appendItems(results.items);
|
|
|
|
this.cursor = results.items[results.items.length - 1];
|
|
|
|
if (dir == -1) {
|
|
this.next = results.more ? true : false;
|
|
} else {
|
|
this.next = true;
|
|
}
|
|
|
|
}.bind(this));
|
|
},
|
|
|
|
_loadAlbums: function() {
|
|
if (this.loadingAlbums == true) {
|
|
return;
|
|
}
|
|
this.loadingAlbums = true;
|
|
|
|
window.fetch("api/v1/albums" + (this.path || ""), function(error, xhr) {
|
|
this.loadingAlbums = false;
|
|
if (error) {
|
|
console.log("Error loading album: " + (this.path || ""));
|
|
console.error(JSON.stringify(error, null, 2));
|
|
return;
|
|
}
|
|
|
|
var results;
|
|
try {
|
|
results = JSON.parse(xhr.responseText);
|
|
} catch (___) {
|
|
this.$.toast.text = "Unable to load/parse album list.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse photos");
|
|
return;
|
|
}
|
|
|
|
this.albums = results;
|
|
}.bind(this));
|
|
},
|
|
|
|
onResize: function(event) {
|
|
this.debounce("resize", function() {
|
|
this.triggerVisibilityChecks();
|
|
var width = Math.max(this.$.placeholder.offsetWidth || 0, 200);
|
|
|
|
this.cols = Math.floor(this.$.thumbnails.clientWidth / width);
|
|
|
|
var calc = width + Math.floor((this.$.thumbnails.clientWidth % width) / this.cols);
|
|
if (calc != this.calcWidth) {
|
|
this.calcWidth = calc;
|
|
}
|
|
}, 100);
|
|
},
|
|
|
|
ready: function() {
|
|
window.addEventListener("hashchange", function(event) {
|
|
this.hash = event.newURL.replace(/^[^#]*/, "");
|
|
}.bind(this), false);
|
|
|
|
/* Hash changes due to anchor clicks aren't firing the 'hashchange'
|
|
* event... possibly due to app-location? */
|
|
window.setInterval(function() {
|
|
if (this.hash != window.location.hash) {
|
|
this.hash = window.location.hash;
|
|
}
|
|
}.bind(this), 100);
|
|
|
|
window.setInterval(function() {
|
|
function isElementInViewport(el) {
|
|
var rect = el.getBoundingClientRect(),
|
|
vWidth = window.innerWidth || doc.documentElement.clientWidth,
|
|
vHeight = window.innerHeight || doc.documentElement.clientHeight;
|
|
|
|
// Return false if it's not in the viewport
|
|
if (rect.right < 0 || rect.bottom < 0
|
|
|| rect.left > vWidth || rect.top > vHeight) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (this.next && isElementInViewport(this.$.magic)) {
|
|
this.loadNextPhotos();
|
|
}
|
|
|
|
}.bind(this), 500);
|
|
|
|
this._loadAlbums();
|
|
this._loadPhotos();
|
|
|
|
this.onResize();
|
|
|
|
document.addEventListener("scroll", this.onScroll.bind(this));
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</dom-module>
|
|
</html>
|