286 lines
8.8 KiB
HTML
Executable File
286 lines
8.8 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/app-header.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-toolbar/app-toolbar.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/paper-button/paper-button.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-spinner/paper-spinner.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-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 {
|
|
}
|
|
|
|
app-toolbar {
|
|
background-color: rgba(64, 0, 64, 0.5);
|
|
color: white;
|
|
}
|
|
|
|
#toast[error] {
|
|
--paper-toast-background-color: red;
|
|
--paper-toast-color: white;
|
|
}
|
|
|
|
.folders > div {
|
|
margin: 0.5em;
|
|
width: 200px;
|
|
height: 200px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#placeholder {
|
|
position: absolute;
|
|
left: -1000px;
|
|
}
|
|
|
|
app-header-layout {
|
|
--layout-fit: {
|
|
overflow-y: hidden !important;
|
|
};
|
|
}
|
|
</style>
|
|
|
|
<app-location route="{{route}}"></app-location>
|
|
|
|
<photo-thumbnail id="placeholder"></photo-thumbnail>
|
|
|
|
<app-header-layout fullbleed has-scrolling-region>
|
|
<app-header slot="header" fixed>
|
|
<paper-spinner active$="[[loading]]" class="thin"></paper-spinner>
|
|
<div>[[path]]</div>
|
|
</app-header>
|
|
<div id="content">
|
|
<div class="thumbnails layout horizontal wrap">
|
|
<template is="dom-repeat" items="[[photos]]">
|
|
<photo-thumbnail width="[[calcWidth]]" on-tap="_imageTap" item="[[item]]"></photo-thumbnail>
|
|
</template>
|
|
</div>
|
|
<div id="magic"></div>
|
|
<div class="layout horizontal">
|
|
<paper-button disabled$="[[!prev]]" on-tap="loadPrevPhotos">prev</paper-button>
|
|
<paper-button disabled$="[[!next]]" on-tap="loadNextPhotos">next</paper-button>
|
|
</div>
|
|
<div class="folders layout horizontal wrap">
|
|
<template is="dom-repeat" items="[[photos.paths]]">
|
|
<div info="[[item]]" on-tap="_pathTap" >[[item.filepath]]</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</app-header-layout>
|
|
<paper-toast id="toast"></paper-toast>
|
|
</template>
|
|
|
|
<script>
|
|
document.addEventListener("WebComponentsReady", function() {
|
|
"use strict";
|
|
Polymer({
|
|
is: "ketr-photos",
|
|
properties: {
|
|
"loading": Boolean,
|
|
"photos": Array,
|
|
prev: {
|
|
type: Boolean,
|
|
value: false
|
|
},
|
|
next: {
|
|
type: Boolean,
|
|
value: false
|
|
}
|
|
},
|
|
|
|
observers: [
|
|
],
|
|
|
|
behaviors: [
|
|
/* @polymerBehavior Polymer.IronResizableBehavior */
|
|
Polymer.IronResizableBehavior
|
|
],
|
|
|
|
listeners: {
|
|
"iron-resize" : "onResize"
|
|
},
|
|
|
|
date: function(item) {
|
|
var datetime = item.taken || item.modified || item.added;
|
|
return datetime.replace(/T.*$/, "");
|
|
},
|
|
|
|
_imageTap: function(event) {
|
|
window.open(this.base + event.model.item.path + "/" + event.model.item.filename, "image");
|
|
},
|
|
|
|
_pathTap: function(event) {
|
|
window.location.href = event.model.item.filepath;
|
|
},
|
|
|
|
loadNextPhotos: function() {
|
|
var cursor = this.photos[this.photos.length - 1];
|
|
this._loadPhotos(cursor.id + "_" + cursor.taken.toString().replace(/T.*/, ""), +1, true);
|
|
},
|
|
|
|
loadPrevPhotos: function() {
|
|
var cursor = this.photos[0];
|
|
this._loadPhotos(cursor.id + "_" + cursor.taken.toString().replace(/T.*/, ""), -1);
|
|
},
|
|
|
|
_loadPhotos: function(start, dir, append) {
|
|
if (this.loading == true) {
|
|
return;
|
|
}
|
|
this.loading = true;
|
|
|
|
dir = dir || +1;
|
|
var params = {
|
|
limit: Math.ceil(this.clientWidth / 200) * Math.ceil(this.clientHeight / 200),
|
|
dir: dir
|
|
}, url = "";
|
|
if (start) {
|
|
params.next = start;
|
|
}
|
|
if (this.sortOrder) {
|
|
params.sort = this.sortOrder;
|
|
}
|
|
for (var key in params) {
|
|
if (url == "") {
|
|
url = "?";
|
|
} else {
|
|
url += "&";
|
|
}
|
|
url += key + "=" + encodeURIComponent(params[key]);
|
|
}
|
|
|
|
window.fetch("api/v1/photos" + url, 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 = "";
|
|
}
|
|
|
|
if (append) {
|
|
results.items.forEach(function(photo) {
|
|
this.push("photos", photo);
|
|
}.bind(this));
|
|
} else {
|
|
this.photos = results.items;
|
|
}
|
|
|
|
if (dir == +1) {
|
|
this.prev = start ? true : false;
|
|
this.next = results.more ? true : false;
|
|
} else {
|
|
this.prev = results.more ? true : false;
|
|
this.next = true;
|
|
}
|
|
|
|
}.bind(this));
|
|
},
|
|
|
|
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);
|
|
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._loadPhotos();
|
|
|
|
this.onResize();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</dom-module>
|
|
</html>
|