2156 lines
72 KiB
HTML
Executable File
2156 lines
72 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-drawer/app-drawer.html">
|
|
<link rel="import" href="../../bower_components/app-route/app-route.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-icons/image-icons.html">
|
|
<link rel="import" href="../../bower_components/iron-icons/maps-icons.html">
|
|
<link rel="import" href="../../bower_components/iron-iconset/iron-iconset.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/iron-flex-layout/iron-flex-layout-classes.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-input/paper-input.html">
|
|
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
|
|
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
|
|
<link rel="import" href="../../bower_components/paper-tabs/paper-tab.html">
|
|
<link rel="import" href="../../bower_components/paper-tabs/paper-tabs.html">
|
|
<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
|
|
|
|
<link rel="import" href="../../bower_components/datetime-picker/calendar-element.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/user-profile.html">
|
|
<link rel="import" href="../../elements/photo-lightbox.html">
|
|
<link rel="import" href="../../elements/photo-thumbnail.html">
|
|
<link rel="import" href="../../elements/ken-burns.html">
|
|
|
|
<script src="fetch.js"></script>
|
|
|
|
<script>'<base href="/BASEPATH/">';</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-flex-factors iron-positioning">
|
|
#login {
|
|
max-width: 50ex;
|
|
margin: 1em auto;
|
|
padding: 2em;
|
|
border: 1px solid #444;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#requestAccess paper-button,
|
|
#login paper-button {
|
|
margin-top: 1em;
|
|
}
|
|
|
|
#header {
|
|
padding: 0.5em;
|
|
background: #ddd;
|
|
box-shadow: 0px 0px 5px black;
|
|
box-sizing: border-box;
|
|
height: 64px;
|
|
}
|
|
|
|
#header iron-pages {
|
|
padding: 0 0.5em;
|
|
}
|
|
|
|
#header [mode='memories'] b:hover {
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
#breadcrumb {
|
|
padding: 0.5em;
|
|
}
|
|
|
|
#loading,
|
|
#bottom {
|
|
margin-top: 3em;
|
|
padding-bottom: 1em;
|
|
}
|
|
|
|
#loading paper-spinner,
|
|
#bottom paper-spinner, {
|
|
padding-bottom: 1em;
|
|
}
|
|
|
|
#albums {
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
#pages > div {
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
height: 100%;
|
|
}
|
|
|
|
#pages > div > div {
|
|
padding: 0.5em;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#pages {
|
|
position: relative;
|
|
height: calc(100vh - 64px);
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#tabs {
|
|
background: #ddd;
|
|
color: #444;
|
|
box-sizing: border-box;
|
|
height: 64px;
|
|
box-shadow: 0px 0px 5px black;
|
|
}
|
|
|
|
#breadcrumb > div {
|
|
margin-right: 0.5em;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#albums div:hover,
|
|
#breadcrumb > div:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
#yearSlider {
|
|
display: none;
|
|
margin-top: 0.5em;
|
|
position: fixed;
|
|
display: block;
|
|
opacity: 0;
|
|
right: 5px;
|
|
bottom: 0px;
|
|
padding: 0.5em;
|
|
width: 120px;
|
|
background-color: rgb(16, 0, 16);
|
|
color: white;
|
|
text-align: center;
|
|
transition: opacity 0.5s ease-in-out;
|
|
-webkit-transition: opacity 0.5s ease-in-out;
|
|
pointer-events: none;
|
|
}
|
|
|
|
#pager {
|
|
margin-top: 0.5em;
|
|
position: fixed;
|
|
display: block;
|
|
opacity: 0;
|
|
right: 5px;
|
|
border-radius: 0.25em;
|
|
padding: 0.5em;
|
|
width: 120px;
|
|
background-color: rgb(16, 0, 16);
|
|
color: white;
|
|
text-align: center;
|
|
transition: opacity 0.5s ease-in-out;
|
|
-webkit-transition: opacity 0.5s ease-in-out;
|
|
pointer-events: none;
|
|
}
|
|
|
|
#toast[error] {
|
|
--paper-toast-background-color: red;
|
|
--paper-toast-color: white;
|
|
}
|
|
|
|
.date-line {
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
padding: 0.5em 0;
|
|
font-weight: bold;
|
|
transition: opacity 0.25s ease-in-out;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.date-line .header {
|
|
padding: 0.25em 3px;
|
|
}
|
|
|
|
@media (max-width: 800px) {
|
|
.date-line {
|
|
font-size: 8pt;
|
|
}
|
|
|
|
photo-thumbnail {
|
|
width: 25vw;
|
|
height: 25vw;
|
|
}
|
|
}
|
|
|
|
photo-thumbnail {
|
|
transition: opacity 0.25s ease-in-out;
|
|
--photo-thumbnail: {
|
|
border: 3px solid white;
|
|
};
|
|
}
|
|
|
|
photo-thumbnail[selected] {
|
|
--photo-thumbnail: {
|
|
border: 3px solid blue;
|
|
};
|
|
}
|
|
|
|
div:focus {
|
|
font-weight: bold;
|
|
outline: none;
|
|
}
|
|
|
|
calendar-element {
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
#memories {
|
|
padding: 0.5em 1em;
|
|
}
|
|
|
|
#memories > div,
|
|
#memories > calendar-element {
|
|
padding-top: 1em;
|
|
}
|
|
|
|
#memories .memory-buttons {
|
|
padding-top: 0;
|
|
font-size: 0.8em;
|
|
}
|
|
|
|
#memories .memory-buttons :not([is-today]) paper-icon-button {
|
|
color: #444;
|
|
}
|
|
|
|
#memories [is-today] {
|
|
font-weight: bold;
|
|
}
|
|
|
|
#loginStatus {
|
|
padding: 1em;
|
|
border: 2px solid #444;
|
|
margin: 1.5em -0.5em;
|
|
color: #222;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#loginStatus iron-icon {
|
|
margin-right: 1em;
|
|
min-width: 1.5em;
|
|
display: inline-block;
|
|
}
|
|
|
|
#loginStatus .title {
|
|
font-weight: bold;
|
|
line-height: 1.5em;
|
|
}
|
|
|
|
#loginStatus .status {
|
|
white-space: pre-line;
|
|
margin-top: 0.5em;
|
|
}
|
|
|
|
#requestAccess {
|
|
max-width: 60ex;
|
|
border: 1px solid #444;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#requestAccess .title {
|
|
padding: 0.5em;
|
|
background-color: #ddd;
|
|
}
|
|
|
|
#requestAccess #createButton {
|
|
margin-top: 1.5em;
|
|
}
|
|
|
|
#holiday > div:first-child {
|
|
font-weight: bold;
|
|
}
|
|
|
|
#holiday [tabindex]:hover {
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
}
|
|
|
|
#holiday [selected] {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.isNext {
|
|
margin-left: 0.5em;
|
|
opacity: 0.5;
|
|
}
|
|
</style>
|
|
|
|
<app-location route="{{route}}"></app-location>
|
|
|
|
<app-drawer-layout id="albumLayout">
|
|
<app-drawer id="drawer" slot="drawer">
|
|
<paper-tabs attr-for-selected="tab" id="tabs" selected="{{mode}}">
|
|
<!--paper-tab tab="time"><paper-icon-button icon="date-range"></paper-icon-button></paper-tab-->
|
|
<paper-tab tab="memories"><paper-icon-button icon="today"></paper-icon-button></paper-tab>
|
|
<paper-tab tab="albums"><paper-icon-button icon="folder"></paper-icon-button></paper-tab>
|
|
<paper-tab tab="holiday"><paper-icon-button icon="redeem"></paper-icon-button></paper-tab>
|
|
<paper-tab tab="slideshow">S</paper-tab>
|
|
<paper-tab hidden$="[[!user.maintainer]]" tab="duplicates"><paper-icon-button icon="compare-arrows"></paper-icon-button></paper-tab>
|
|
<paper-tab hidden$="[[!user.maintainer]]" tab="trash"><paper-icon-button icon="delete"></paper-icon-button></paper-tab>
|
|
</paper-tabs>
|
|
<iron-pages id="pages" attr-for-selected="id" selected="[[mode]]" fallback-selection="loading">
|
|
<div id="loading"></div>
|
|
<div id="slideshow" class="flex layout vertical">
|
|
</div>
|
|
<div id="holiday" class="flex layout vertical">
|
|
<div>Holidays</div>
|
|
<template is="dom-repeat" items="[[holidays]]">
|
|
<div tabindex="0" holiday on-tap="loadHoliday">[[item]]</div>
|
|
</template>
|
|
</div>
|
|
<div id="time"><div>... time slider ...</div></div>
|
|
<div id="memories" class="flex layout vertical center">
|
|
<div class="memory-buttons layout self-stretch horizontal around-justified">
|
|
<div is-today$=[[!isToday(date)]] on-tap="gotoRandomDay" class="layout vertical center">
|
|
<div>Random</div>
|
|
<paper-icon-button icon="refresh"></paper-icon-button>
|
|
</div>
|
|
<div is-today$=[[isToday(date)]] on-tap="gotoToday" class="layout vertical center">
|
|
<div>Today</div>
|
|
<paper-icon-button icon="today"></paper-icon-button>
|
|
</div>
|
|
</div>
|
|
<calendar-element
|
|
id="calendar"
|
|
tabindex
|
|
min="[[year]]-01-01"
|
|
max="[[year]]-12-31"
|
|
on-date-changed="calendarChanged"></calendar-element>
|
|
<div>Pick a date on the calendar to look back in time and see photos from that day.</div>
|
|
<div hidden$="[[!thumbnails.length]]">
|
|
<div>On <b>[[memoryDate]]</b>, there have been <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos taken over <b>[[years.length]]</b> year(s).</div>
|
|
</div>
|
|
</div>
|
|
<div hidden$="[[!user.maintainer]]" id="trash" class="flex layout vertical">
|
|
<div><b>Trash</b></div>
|
|
<div>
|
|
<p>There are <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos in the trash.</p>
|
|
<p>Files in the trash will be removed after 60 days.</p></div>
|
|
<div hidden$="[[!add(thumbnails.length,pendingPhotos.length)]]" class="layout vertical">
|
|
<div>Do you want to empty the trash now?</div>
|
|
<paper-button on-tap="purgeTrashAction">empty trash</paper-button>
|
|
<div><p><b>NOTE:</b> This can not be undone.</p></div>
|
|
</div>
|
|
</div>
|
|
<div hidden$="[[!user.maintainer]]" id="duplicates" class="flex layout vertical">
|
|
<div><b>Duplicate names</b></div>
|
|
<div>There are <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos which may be duplicates
|
|
based on either their name.</div>
|
|
<div>Look for duplicates in each file-name pair. If they are not the same,
|
|
tap <iron-icon icon="text-format"></iron-icon> on the photo and that one will be renamed
|
|
on the server by adding four letters from the image's signature to the name.</div>
|
|
<div>If they are duplicates, you can tap <iron-icon icon="delete"></iron-icon> to move the
|
|
photo to the trash.</div>
|
|
</div>
|
|
<div id="albums" class="flex layout vertical">
|
|
<template is="dom-repeat" items="[[breadcrumb(path)]]">
|
|
<div tabindex="0" on-tap="loadPath">[[item.name]] /</div>
|
|
</template>
|
|
<template is="dom-repeat" items="[[albums.children]]">
|
|
<div tabindex="0" on-tap="loadPath">[[item.name]]</div>
|
|
</template>
|
|
</div>
|
|
</iron-pages>
|
|
</app-drawer>
|
|
<app-header-layout>
|
|
<app-header reveals slot="header">
|
|
<div id="header" class="layout horizontal center">
|
|
<paper-icon-button hidden$="[[!user]]" icon="search" on-tap="drawerToggle"></paper-icon-button>
|
|
<iron-pages class="flex" attr-for-selected="mode" selected="[[mode]]">
|
|
<div id="loading"></div>
|
|
<div mode="login"><div>You are not logged in.</div></div>
|
|
<div mode="albums" id="breadcrumb" class="horizontal layout center">
|
|
<template is="dom-repeat" items="[[breadcrumb(path)]]">
|
|
<div tabindex="0" on-tap="loadPath">[[item.name]] /</div>
|
|
</template>
|
|
</div>
|
|
<div mode="slideshow" class="layout horizontal">
|
|
<div>[[slideshowInfo]]</div>
|
|
</div>
|
|
<div mode="holiday" class="layout horizontal">
|
|
<div>[[holidayTitle]]</div>
|
|
<div class='isNext' hidden$="[[!isNextHoliday(holidayTitle, nextHoliday)]]">(next upcoming Holiday!!)</div>
|
|
</div>
|
|
<div mode="time">time</div>
|
|
<div mode="memories">Photos taken on <b on-tap="drawerToggle">[[memoryDate]]</b></div>
|
|
</iron-pages>
|
|
<div>
|
|
<div hidden$="[[user]]">
|
|
<!--paper-button>login</paper-button-->
|
|
</div>
|
|
<div hidden$="[[!user]]">
|
|
<paper-icon-button on-tap="profile" icon="account-box"></paper-icon-button>
|
|
<paper-button on-tap="logout">logout</paper-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</app-header>
|
|
<iron-pages attr-for-selected="id" selected="[[mode]]" fallback-selection="content">
|
|
<div id="loading" class="layout vertical center">
|
|
<paper-spinner active$="[[loading]]" class="thin"></paper-spinner>
|
|
<div>~ Loading ~</div>
|
|
</div>
|
|
<user-profile id="profile"></user-profile>
|
|
<div id="slideshow"><ken-burns id="kenBurns"></ken-burns></div>
|
|
<div id="content">
|
|
<div id="thumbnails" class="layout horizontal wrap"></div>
|
|
<div id="bottom" class="layout vertical center">
|
|
<paper-spinner hidden$="[[!loading]]" active$="[[loading]]" class="thin"></paper-spinner>
|
|
<div hidden$="[[loading]]">~ the end ~</div>
|
|
</div>
|
|
</div>
|
|
<div id="login" class="layout horizontal center">
|
|
<div class="flex layout vertical">
|
|
<div id="instructions">
|
|
<p><b>ketrenos.com</b> is a personal website for my family and friends.</p>
|
|
<p>If you already have an email account on this domain, you can login to the photo viewer
|
|
using your normal @ketrenos.com account name, and password.</p>
|
|
</p>If you are a friend or family member (immediate, or extended)
|
|
<a on-tap="requestAccess" href="#">request access</a>,
|
|
provide your email address, and tell me who in the extended Ketrenos
|
|
universe you know. If you're not a bot, I'll very likely give you access :)</p>
|
|
</div>
|
|
<div id="loginStatus" hidden$="[[!loginStatus]]" class="layout vertical justified start">
|
|
<div class="layout horizontal center">
|
|
<iron-icon icon="info-outline"></iron-icon>
|
|
<div class="title self-start">[[loginStatusTitle]]</div>
|
|
</div>
|
|
<div class="status self-start">[[loginStatus]]</div>
|
|
</div>
|
|
<paper-input tabindex=0 autofocus id="username" label="User ID" value="{{username}}" on-keypress="enterCheck"></paper-input>
|
|
<paper-input tabindex=0 id="password" label="Password" type="password" value="{{password}}" on-keypress="enterCheck"></paper-input>
|
|
<paper-button tabindex=0 id="loginButton" disabled$="[[disableLogin(username,password)]]" on-tap="login" raised><div hidden$="[[loggingIn]]">login</div><div hidden$="[[!loggingIn]]"><paper-spinner active$="[[loggingIn]]"></paper-spinner></div></paper-button>
|
|
</div>
|
|
</div>
|
|
</iron-pages>
|
|
</app-header-layout>
|
|
</app-drawer-layout>
|
|
<div id="pager"></div>
|
|
<div id="yearSlider">
|
|
<template is="dom-repeat" items="[[years]]">
|
|
<div>[[item]]</div>
|
|
</template>
|
|
</div>
|
|
<paper-dialog id="requestAccess" with-backdrop>
|
|
<div class="layout vertical">
|
|
<div class="title">Create an account</div>
|
|
<div>
|
|
<p>To have your account activated, tell me who you know in the 'who do you know?' field. You will receive
|
|
an email with a verification link. Check your email and click the link to verify your email address.</p>
|
|
<p>Once verified, I can grant you access to the system.
|
|
</p>
|
|
<p>Thanks,<br>
|
|
James</p>
|
|
</div>
|
|
<paper-input tabindex=0 autofocus id="mail" label="E-mail" value="{{username}}" on-keypress="enterCheck"></paper-input>
|
|
<paper-input tabindex=0 id="password" label="Password" type="password" value="{{password}}" on-keypress="enterCheck"></paper-input>
|
|
<paper-input tabindex=0 id="name" label="Display name" value="{{name}}" on-keypress="enterCheck"></paper-input>
|
|
<paper-input tabindex=0 id="who" label="Who do you know?" value="{{who}}" on-keypress="enterCheck"></paper-input>
|
|
<paper-button tabindex=0 id="createButton" disabled$="[[disableCreate(username,password,name,who)]]" on-tap="create" raised><div hidden$="[[loggingIn]]">create</div><div hidden$="[[!loggingIn]]"><paper-spinner active$="[[loggingIn]]"></paper-spinner></div></paper-button>
|
|
</div>
|
|
</paper-dialog>
|
|
<paper-toast id="toast"></paper-toast>
|
|
<photo-lightbox tabindex="0"
|
|
id="lightbox"
|
|
on-close="lightBoxClose"
|
|
on-next="lightBoxNext"
|
|
on-previous="lightBoxPrevious"
|
|
actions="[[actions]]"
|
|
on-action="_imageAction"></photo-lightbox>
|
|
</template>
|
|
|
|
<script>
|
|
document.addEventListener("WebComponentsReady", function() {
|
|
"use strict";
|
|
Polymer({
|
|
is: "ketr-photos",
|
|
properties: {
|
|
year: {
|
|
type: String,
|
|
value: window.moment().format("YYYY")
|
|
},
|
|
actions: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
password: {
|
|
type: String,
|
|
value: ""
|
|
},
|
|
loginStatus: {
|
|
type: String,
|
|
value: ""
|
|
},
|
|
username: {
|
|
type: String,
|
|
value: ""
|
|
},
|
|
name: {
|
|
type: String,
|
|
value: ""
|
|
},
|
|
years: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
user: {
|
|
type: Object,
|
|
value: null
|
|
},
|
|
loading: Boolean,
|
|
pendingPhotos: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
next: {
|
|
type: Boolean,
|
|
value: false
|
|
},
|
|
breakOnDayChange: {
|
|
type: Boolean,
|
|
value: true,
|
|
reflectToAttribute: true
|
|
},
|
|
path: {
|
|
type: String,
|
|
value: ""
|
|
},
|
|
visibleThumbs: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
thumbnails: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
mode: {
|
|
type: String,
|
|
value: "loading"
|
|
},
|
|
days: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
daysGrouped: {
|
|
type: Array,
|
|
value: []
|
|
},
|
|
date: {
|
|
type: String,
|
|
value: window.moment().format("YYYY-MM-DD")
|
|
},
|
|
holiday: {
|
|
type: String,
|
|
value: "Christmas"
|
|
}
|
|
},
|
|
|
|
observers: [
|
|
"routeChanged(route)",
|
|
"widthChanged(calcWidth)",
|
|
"modeChanged(mode)",
|
|
"dateChanged(date)",
|
|
"userChanged(user)",
|
|
"daysChanged(days)",
|
|
"baseRouteChanged(route.path, basePath)",
|
|
"pathChanged(path, route.path, basePath)"
|
|
],
|
|
|
|
listeners: {
|
|
"scroll": "onScroll",
|
|
"iron-resize" : "onResize"
|
|
},
|
|
|
|
isNextHoliday: function(holiday, nextHoliday) {
|
|
return (holiday == nextHoliday);
|
|
},
|
|
|
|
pathChanged: function(path, routePath, basePath) {
|
|
if (!basePath || !routePath || !path) {
|
|
return;
|
|
}
|
|
var tmp = routePath.replace(basePath, "");
|
|
if (tmp != path) {
|
|
this.set("route.path", basePath + path);
|
|
}
|
|
},
|
|
|
|
baseRouteChanged: function(routePath, basePath) {
|
|
if (!basePath || !routePath) {
|
|
return;
|
|
}
|
|
this.path = routePath.replace(basePath, "");
|
|
console.log("Album: " + routePath.replace(basePath, ""));
|
|
},
|
|
|
|
routeChanged: function(route) {
|
|
console.log(JSON.stringify(route));
|
|
},
|
|
|
|
disableLogin: function(username, password) {
|
|
return !username || username == "" || !password || password == "";
|
|
},
|
|
|
|
disableCreate: function(username, password, name, who) {
|
|
return !username || username == "" ||
|
|
!password || password == "" ||
|
|
!name || name == "" ||
|
|
!who || who == "";
|
|
},
|
|
|
|
enterCheck: function(event) {
|
|
if (event.key == 'Enter') {
|
|
event.preventDefault();
|
|
var next = event.currentTarget.nextElementSibling;
|
|
while (next && !next.hasAttribute("tabindex")) {
|
|
next = event.currentTarget.nextElementSibling;
|
|
}
|
|
|
|
if (!next) {
|
|
return;
|
|
}
|
|
|
|
if (next.tagName.toLowerCase() == "paper-button") {
|
|
if (!next.disabled) {
|
|
next.click();
|
|
}
|
|
} else {
|
|
this.async(function(next) {
|
|
next._focusableElement.focus();
|
|
}.bind(this, next), 100);
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
calendarChanged: function(event) {
|
|
if (!this.ignoreCalendar) {
|
|
this.date = event.detail.value;
|
|
}
|
|
},
|
|
|
|
requestAccess: function(event) {
|
|
this.$.requestAccess.open();
|
|
},
|
|
|
|
add: function(a, b) {
|
|
return parseInt(a) + parseInt(b);
|
|
},
|
|
|
|
isToday: function(date) {
|
|
return this.date == window.moment().format("YYYY-MM-DD");
|
|
},
|
|
|
|
gotoToday: function() {
|
|
this.date = window.moment().format("YYYY-MM-DD");
|
|
},
|
|
|
|
gotoRandomDay: function() {
|
|
var day;
|
|
if (this.daysGrouped.length == 0) {
|
|
this.photosToday = 0;
|
|
day = window.moment(Math.ceil(Math.random() * 365), "DDD").format("MM-DD");
|
|
} else {
|
|
var groupedDay = this.daysGrouped[Math.floor(this.daysGrouped.length * Math.random())];
|
|
this.photosToday = groupedDay.count;
|
|
day = groupedDay.date;
|
|
}
|
|
this.date = window.moment().format("YYYY-") + day;
|
|
if (this.date != this.$.calendar.date) {
|
|
console.log(this.date + " != " + this.$.calendar.date + ": calendar-element work around will be used.");
|
|
}
|
|
},
|
|
|
|
login: function(event) {
|
|
if (this.loading) {
|
|
return;
|
|
}
|
|
this.loading = true;
|
|
this.loggingIn = true;
|
|
this.user = null;
|
|
window.fetch("api/v1/users/login", function(error, xhr) {
|
|
this.loggingIn = false;
|
|
this.loading = false;
|
|
// this.password = "";
|
|
var user;
|
|
|
|
if (error) {
|
|
this.user = null;
|
|
this.$.toast.text = error;
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Invalid login information.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
user = JSON.parse(xhr.responseText);
|
|
} catch(___) {
|
|
this.$.toast.text = "Unable to load/parse user information.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse user information");
|
|
return;
|
|
}
|
|
|
|
this.user = user;
|
|
}.bind(this), null, "POST", { u: this.username, p: this.password });
|
|
},
|
|
|
|
create: function(event) {
|
|
if (this.loading) {
|
|
return;
|
|
}
|
|
this.loading = true;
|
|
this.loggingIn = true;
|
|
this.user = null;
|
|
window.fetch("api/v1/users/create", function(error, xhr) {
|
|
this.loggingIn = false;
|
|
this.loading = false;
|
|
// this.password = "";
|
|
var user;
|
|
|
|
this.$.requestAccess.close();
|
|
|
|
if (error) {
|
|
this.user = null;
|
|
this.$.toast.text = error;
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Invalid login information.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
user = JSON.parse(xhr.responseText);
|
|
} catch(___) {
|
|
this.$.toast.text = "Unable to load/parse user information.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse user information");
|
|
return;
|
|
}
|
|
|
|
if (user && user.username) {
|
|
this.user = user;
|
|
}
|
|
}.bind(this), null, "POST", {
|
|
w: this.who,
|
|
p: this.password,
|
|
n: this.name,
|
|
m: this.username
|
|
});
|
|
},
|
|
|
|
profile: function(event) {
|
|
if (this.mode == "profile") {
|
|
this.mode = this.lastMode;
|
|
} else {
|
|
this.$.profile.user = this.user;
|
|
this.lastMode = this.mode;
|
|
this.mode = "profile";
|
|
}
|
|
},
|
|
|
|
logout: function(event) {
|
|
this.path = "";
|
|
window.fetch("api/v1/users/logout", function(error, xhr) {
|
|
this.user = this.name = this.password = this.username = this.who = null;
|
|
}.bind(this));
|
|
},
|
|
|
|
changeMode: function(event) {
|
|
var mode = event.currentTarget.icon;
|
|
if (this.mode != mode) {
|
|
this.mode = mode;
|
|
}
|
|
},
|
|
|
|
resetPhotos: function() {
|
|
this.albums = [];
|
|
this.years = [];
|
|
this.notifyPath("years.length");
|
|
this.pendingPhotos = [];
|
|
this.notifyPath("pendingPhotos.length");
|
|
this.visibleThumbs = [];
|
|
this.thumbnails = [];
|
|
this.notifyPath("thumbnails.length");
|
|
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
|
this.limit = undefined;
|
|
},
|
|
|
|
dateChanged: function(date) {
|
|
this.resetPhotos();
|
|
this.memoryDate = window.moment(this.date).format("MMMM Do");
|
|
this._loadPhotos();
|
|
|
|
this.ignoreCalendar = true;
|
|
this.$.calendar.date = date;
|
|
/* There is a bug in calendar-element that sets the wrong date */
|
|
if (this.$.calendar.date != date) {
|
|
this.async(function() {
|
|
console.log("calendar-element work-around attempt");
|
|
this.$.calendar.date = this.date;
|
|
this.ignoreCalendar = false;
|
|
}, 100);
|
|
|
|
/* Keep ignoring the calendar change until the async call
|
|
* finishes */
|
|
return;
|
|
}
|
|
this.ignoreCalendar = false;
|
|
},
|
|
|
|
modeChanged: function(mode) {
|
|
if (!mode) {
|
|
return;
|
|
}
|
|
console.log("Mode changed to " + mode);
|
|
this.setActions();
|
|
this.resetPhotos();
|
|
this._loadAlbums();
|
|
this._loadDays();
|
|
this._loadPhotos();
|
|
},
|
|
|
|
breadcrumb: function(path) {
|
|
var crumbs = path.replace(/(.*)\/$/, "/$1").split("/"), parts = [];
|
|
path = "";
|
|
crumbs.forEach(function(crumb, index) {
|
|
if (index > 0) {
|
|
path += crumb + "/";
|
|
}
|
|
parts.push({
|
|
name: crumb ? crumb : "Top",
|
|
path: path
|
|
})
|
|
});
|
|
return parts;
|
|
},
|
|
|
|
drawerToggle: function(event) {
|
|
if (this.$.drawer.opened) {
|
|
this.$.drawer.close();
|
|
this.$.albumLayout.forceNarrow = true;
|
|
this.$.drawer.resetLayout();
|
|
} else {
|
|
this.$.drawer.open();
|
|
this.$.albumLayout.forceNarrow = false;
|
|
if (window.innerWidth >= 800) {
|
|
this.$.drawer.persistent = true;
|
|
}
|
|
if (this.mode == "memories") {
|
|
this.async(function() {
|
|
this.$.calendar.$.days.focus();
|
|
}, 100);
|
|
}
|
|
}
|
|
},
|
|
|
|
highlightHoliday: function(holiday) {
|
|
Array.prototype.forEach.call(this.$.holiday.querySelectorAll("[selected]"), function(el) {
|
|
el.removeAttribute("selected");
|
|
});
|
|
Array.prototype.forEach.call(this.$.holiday.querySelectorAll("[holiday]"), function(el) {
|
|
if (el.textContent == holiday) {
|
|
el.setAttribute("selected", true);
|
|
}
|
|
});
|
|
},
|
|
|
|
loadHoliday: function(event) {
|
|
this.holiday = event.model.item;
|
|
this.highlightHoliday(this.holiday);
|
|
if (this.mode == "holiday") {
|
|
this.resetPhotos();
|
|
this._loadPhotos();
|
|
}
|
|
},
|
|
|
|
loadPath: function(event) {
|
|
this._pathLoad(event.model.item.path);
|
|
},
|
|
|
|
loadAlbum: function(event) {
|
|
this._pathLoad(event.detail);
|
|
},
|
|
|
|
_pathLoad: function(path) {
|
|
this.path = path;
|
|
this.resetPhotos();
|
|
this._loadAlbums();
|
|
this._loadDays();
|
|
this._loadPhotos();
|
|
},
|
|
|
|
triggerLoadMore: function() {
|
|
if (this.$.bottom.getBoundingClientRect().bottom < window.innerHeight + 200) {
|
|
if (this.pendingPhotos.length) {
|
|
console.log("At bottom - more items; processItems requested.");
|
|
this.async(this.processItems.bind(this));
|
|
} else {
|
|
console.log("At bottom - no more items.");
|
|
}
|
|
}
|
|
},
|
|
|
|
onScroll: function(event) {
|
|
if (this.disableScrolling) {
|
|
if (event) event.preventDefault();
|
|
window.scrollTo(this.topStickX, this.topStickY);
|
|
return;
|
|
}
|
|
|
|
this.triggerLoadMore();
|
|
|
|
var headerHeight = this.$.header.offsetHeight;
|
|
|
|
var i, date = null, top = null, lowest = window.innerHeight;
|
|
for (i = 0; i < this.visibleThumbs.length; i++) {
|
|
var thumb = this.thumbnails[this.visibleThumbs[i]], rect = thumb.getBoundingClientRect();
|
|
/* If no thumb is chosen yet, or this one is the highest on the page, use it */
|
|
if (rect.top > 0 && (!top || lowest > rect.top)) {
|
|
lowest = rect.top
|
|
top = thumb;
|
|
}
|
|
}
|
|
|
|
if (top && (this.mode == "memories" || this.mode == "albums")) {
|
|
var photo = top.item;
|
|
this.$.pager.style.opacity = 1;
|
|
var date = window.moment(new Date((photo.taken || photo.modified || photo.added).replace(/T.*/, " GMT")));
|
|
date = date.calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, "");
|
|
this.$.pager.textContent = date;
|
|
this.$.pager.style.top = headerHeight +
|
|
Math.floor((window.innerHeight - 2 * headerHeight) * (window.scrollY / document.scrollingElement.scrollHeight)) + 'px';
|
|
|
|
this.debounce("hide-pager", function() {
|
|
this.$.pager.style.opacity = 0;
|
|
}, 250);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (this.processing) {
|
|
console.log("hide-or-show while processing");
|
|
this.async(this.triggerVisibilityChecks.bind(this), 100);
|
|
return;
|
|
}
|
|
|
|
this.processing = true;
|
|
|
|
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();
|
|
this.processing = false;
|
|
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;
|
|
}
|
|
}
|
|
if (this.thumbnails[index]) {
|
|
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;
|
|
|
|
this.processing = false;
|
|
});
|
|
},
|
|
|
|
lightBoxClose: function(event) {
|
|
this.disableScrolling = false;
|
|
if (this.lightBoxElement) {
|
|
this.lightBoxElement.selected = false;
|
|
this.updateStyles();
|
|
}
|
|
},
|
|
|
|
lightBoxSet: function(index) {
|
|
/* If lightBoxNext / lightBoxPrev is called after the last image
|
|
* was deleted, close the lightbox instead of trying to cycle */
|
|
if (index >= this.thumbnails.length) {
|
|
this.$.lightbox.close();
|
|
} else {
|
|
this.loadLightbox(this.thumbnails[index]);
|
|
}
|
|
|
|
this.debounce("load-check", function() {
|
|
this.triggerLoadMore();
|
|
this.triggerVisibilityChecks();
|
|
}, 150);
|
|
},
|
|
|
|
findThumbnail: function(id) {
|
|
for (var i = 0; i < this.thumbnails.length; i++) {
|
|
if (this.thumbnails[i].item.id == id) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
lightBoxNext: function() {
|
|
var index = this.findThumbnail(this.lightBoxElement.item.id);
|
|
if (index == -1 || (index + 1) >= this.thumbnails.length) {
|
|
index = 0;
|
|
} else {
|
|
index++;
|
|
}
|
|
this.lightBoxSet(index);
|
|
},
|
|
|
|
|
|
lightBoxPrevious: function() {
|
|
var index = this.findThumbnail(this.lightBoxElement.item.id);
|
|
if (index == -1 || index == 0) {
|
|
index = this.thumbnails.length - 1;
|
|
} else {
|
|
index--;
|
|
}
|
|
|
|
this.lightBoxSet(index);
|
|
},
|
|
|
|
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
|
|
],
|
|
|
|
loadLightbox: function(el) {
|
|
if (this.lightBoxElement) {
|
|
this.lightBoxElement.selected = false;
|
|
}
|
|
|
|
this.$.lightbox.item = el.item;
|
|
this.$.lightbox.src = this.base + el.item.path + "thumbs/scaled/" + el.item.filename;
|
|
this.lightBoxElement = el;
|
|
this.lightBoxElement.selected = true;
|
|
this.disableScrolling = true;
|
|
if (this.$.lightbox.opened) {
|
|
this.lightBoxElement.scrollIntoView(false);
|
|
} else {
|
|
this.$.lightbox.open();
|
|
}
|
|
this.topStickX = window.scrollX;
|
|
this.topStickY = window.scrollY;
|
|
|
|
this.updateStyles();
|
|
},
|
|
|
|
_imageTap: function(event) {
|
|
this.loadLightbox(event.currentTarget);
|
|
},
|
|
|
|
_pathTap: function(event) {
|
|
window.location.href = event.model.item.filepath;
|
|
},
|
|
|
|
appendItems: function(photos) {
|
|
if (!this.pendingPhotos || !photos || photos.length == 0) {
|
|
return;
|
|
}
|
|
|
|
photos = photos.filter(function(photo) {
|
|
var year = (photo.taken || photo.modified || photo.added || "").replace(/^(....).*$/, "$1");
|
|
if (!year) {
|
|
console.log("Partial photo record received: " + photo.id);
|
|
return false;
|
|
}
|
|
if (this.years.indexOf(year) == -1) {
|
|
this.push("years", year);
|
|
}
|
|
return true;
|
|
}.bind(this));
|
|
|
|
this.pendingPhotos = this.pendingPhotos.concat(photos);
|
|
this.notifyPath("pendingPhotos.length");
|
|
this.async(this.processItems.bind(this));
|
|
|
|
console.log("Total pending: " + this.pendingPhotos.length);
|
|
|
|
if (this.mode == "slideshow" && !this.slideshowTimeout) {
|
|
this.slideshowNext();
|
|
}
|
|
},
|
|
|
|
slideshowNext: function() {
|
|
if (this.mode != "slideshow") {
|
|
window.cancelTimeout(this.slideshowTimeout);
|
|
delete this.slideshowTimeout;
|
|
return;
|
|
}
|
|
|
|
if (this.slideshowTimeout) {
|
|
window.cancelTimeout(this.slideshowTimeout);
|
|
delete this.slideshowTimeout;
|
|
}
|
|
|
|
if (!this.thumbnails.length) {
|
|
this.slideshowTimeout = window.setTimeout(this.slideshowNext.bind(this), 150);
|
|
return;
|
|
}
|
|
|
|
this.slideshowTimeout = window.setTimeout(this.slideshowNext.bind(this), 24 * 1000);
|
|
|
|
var photo = this.thumbnails[Math.floor(this.thumbnails.length * Math.random())].item;
|
|
this.$.kenBurns.item = photo;
|
|
this.$.kenBurns.src = this.base + photo.path + "thumbs/scaled/" + photo.filename;
|
|
var datetime = (photo.taken || photo.modified || photo.added).replace(/T.*/, "");
|
|
this.slideshowInfo = "In the year of " + datetime.replace(/(....).*/, "$1");
|
|
},
|
|
|
|
|
|
/* Asynchronously load items into the DOM */
|
|
processItems: function() {
|
|
if (this.processing) {
|
|
console.log("processItems while processing");
|
|
this.async(this.processItems.bind(this), 100);
|
|
return;
|
|
}
|
|
|
|
if (this.pendingPhotos.length == 0) {
|
|
return;
|
|
}
|
|
|
|
this.processing = true;
|
|
|
|
var lastPath = null;
|
|
var albums = this.querySelectorAll(".album-line");
|
|
if (albums.length) {
|
|
lastPath = albums[albums.length - 1];
|
|
}
|
|
|
|
var photos = this.pendingPhotos.splice(0, 50);
|
|
this.notifyPath("pendingPhotos.length");
|
|
console.log(this.pendingPhotos.length + " photos remain to be converted to photo-thumbnail");
|
|
|
|
for (var i = 0; i < photos.length; i++) {
|
|
var photo = photos[i], datetime;
|
|
|
|
var j;
|
|
for (j = 0; j < this.thumbnails.length; j++) {
|
|
if (this.thumbnails[j].item.id == photo.id) {
|
|
break;
|
|
}
|
|
}
|
|
if (j != this.thumbnails.length) {
|
|
console.log("Photo already exists in thumbnails list. Did server return dups?");
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
datetime = (photo.taken || photo.modified || photo.added).replace(/T.*/, "");
|
|
} catch (error) {
|
|
console.log(JSON.stringify(photo, null, 2));
|
|
throw error;
|
|
}
|
|
|
|
if (this.mode == "duplicates") {
|
|
var name = photo.filename.replace(/[. '"]/g, "_"),
|
|
nameBlock = this.root.querySelector("#name-" + name), thumbnails;
|
|
if (!nameBlock) {
|
|
nameBlock = document.createElement("div");
|
|
nameBlock.id = "name-" + name;
|
|
nameBlock.classList.add("date-line");
|
|
nameBlock.classList.add("layout");
|
|
nameBlock.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 = photo.filename;
|
|
Polymer.dom(nameBlock).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(nameBlock).appendChild(thumbnails);
|
|
Polymer.dom(this.$.thumbnails).appendChild(nameBlock);
|
|
} else {
|
|
thumbnails = Polymer.dom(nameBlock).querySelector(".thumbnails");
|
|
}
|
|
} else if (this.mode == "trash") {
|
|
var trashBlock = this.root.querySelector("#trash-images"), thumbnails;
|
|
if (!trashBlock) {
|
|
trashBlock = document.createElement("div");
|
|
trashBlock.id = "trash-images";
|
|
trashBlock.classList.add("date-line");
|
|
trashBlock.classList.add("layout");
|
|
trashBlock.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 = "Trash";
|
|
Polymer.dom(trashBlock).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(trashBlock).appendChild(thumbnails);
|
|
Polymer.dom(this.$.thumbnails).appendChild(trashBlock);
|
|
} else {
|
|
thumbnails = Polymer.dom(trashBlock).querySelector(".thumbnails");
|
|
}
|
|
} else {
|
|
var dateBlock = this.root.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");
|
|
if (this.mode == "memories") {
|
|
var ago = window.moment(datetime, "YYYY-MM-DD").fromNow();
|
|
ago = ago.charAt(0).toUpperCase() + ago.substr(1);
|
|
div.innerHTML = "<b>" + ago + "</b><br><span style='font-size: 0.8em;font-weight: normal'>" + window.moment(datetime, "YYYY-MM-DD").calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, "") + "</span>";
|
|
} else {
|
|
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.mode == "albums") {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
var thumbnail = document.createElement("photo-thumbnail");
|
|
thumbnail.item = photo;
|
|
if (this.user.maintainer) {
|
|
thumbnail.actions = this.actions;
|
|
thumbnail.addEventListener("action", this._imageAction.bind(this));
|
|
}
|
|
thumbnail.addEventListener("load-image", this._imageTap.bind(this));
|
|
thumbnail.addEventListener("load-album", this.loadAlbum.bind(this));
|
|
|
|
Polymer.dom(thumbnails).appendChild(thumbnail);
|
|
this.thumbnails.push(thumbnail);
|
|
this.notifyPath("thumbnails.length");
|
|
}
|
|
|
|
/* If the viewport is at the bottom when it finishes processing, trigger to load more. */
|
|
this.onScroll();
|
|
|
|
this.processing = false;
|
|
},
|
|
|
|
_removeImageAfterFetch: function(thumbnail, callback, error) {
|
|
thumbnail.disabled = false;
|
|
if (error) {
|
|
console.log("Unable to take action on photo: " + error);
|
|
return;
|
|
}
|
|
thumbnail.style.pointerEvents = "none";
|
|
thumbnail.style.opacity = 0;
|
|
|
|
this.async(function(thumbnail) {
|
|
var parent = thumbnail.parentElement;
|
|
|
|
var i = this.thumbnails.indexOf(thumbnail);
|
|
if (i != -1) {
|
|
if (thumbnail.visible) {
|
|
thumbnail.visible = false;
|
|
/* This changes the index of all thumbnails, so invalidate the entire
|
|
* visibleThumbs list */
|
|
this.visibleThumbs = [];
|
|
}
|
|
this.thumbnails.splice(i, 1);
|
|
this.notifyPath("thumbnails.length");
|
|
|
|
/* Invoke callback once thumbnails has changed */
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
if (!parent) {
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
return;
|
|
}
|
|
|
|
Polymer.dom(parent).removeChild(thumbnail);
|
|
this.triggerLoadMore();
|
|
|
|
if (parent.querySelectorAll("photo-thumbnail").length != 0) {
|
|
this.triggerVisibilityChecks();
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* There are no more thumbnails, so look up the document ancestors
|
|
* until we find the element with the date-line class, and then
|
|
* remove it from it's parent */
|
|
while (parent && !parent.classList.contains("date-line")) {
|
|
parent = parent.parentElement;
|
|
}
|
|
if (!parent) {
|
|
this.triggerVisibilityChecks();
|
|
return;
|
|
}
|
|
|
|
parent.style.opacity = 0;
|
|
|
|
this.async(function(parent) {
|
|
Polymer.dom(this.$.thumbnails).removeChild(parent);
|
|
this.triggerVisibilityChecks();
|
|
}.bind(this, parent), 250);
|
|
}.bind(this, thumbnail), 250);
|
|
},
|
|
|
|
undoAction: function(thumbnail) {
|
|
thumbnail.disabled = true;
|
|
window.fetch("api/v1/photos/" + thumbnail.item.id + "?a=undelete", function(thumbnail, error) {
|
|
thumbnail.disabled = false;
|
|
|
|
if (error) {
|
|
console.log("Unable to take action on photo: " + error);
|
|
return;
|
|
}
|
|
|
|
/* If we are in the lightbox mode, then reload the corresponing
|
|
* thumb in the main view as well */
|
|
if (thumbnail.tagName == "PHOTO-LIGHTBOX") {
|
|
console.log("Undo from lightbox");
|
|
var index = this.findThumbnail(thumbnail.item.id);
|
|
if (index == -1) {
|
|
this.$.lightbox.close();
|
|
return;
|
|
}
|
|
thumbnail = this.thumbnails[index];
|
|
if (this.thumbnails.length == 1) {
|
|
this.$.lightbox.close();
|
|
} else {
|
|
this.lightBoxNext();
|
|
}
|
|
} else {
|
|
console.log("Undo from thumbnail");
|
|
}
|
|
|
|
this._removeImageAfterFetch(thumbnail);
|
|
}.bind(this, thumbnail), {}, "PUT");
|
|
},
|
|
|
|
purgeStatus: function() {
|
|
console.log("Checking purge status", this.purgeTask);
|
|
window.fetch("api/v1/photos/status/" + this.purgeTask.token, function(error, xhr) {
|
|
if (error && xhr.status != 404) {
|
|
console.log("Unable to take action on photo: " + error);
|
|
return;
|
|
}
|
|
|
|
if (xhr.status == 404) {
|
|
console.log("Task complete");
|
|
this.purgeTask = null;
|
|
this.resetPhotos();
|
|
return;
|
|
}
|
|
|
|
var results;
|
|
try {
|
|
results = JSON.parse(xhr.responseText);
|
|
} catch (___) {
|
|
this.$.toast.text = "Unable to parse delete task.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse photos");
|
|
return;
|
|
}
|
|
|
|
if (results.remaining == 0) {
|
|
console.log("Task complete");
|
|
this.purgeTask = null;
|
|
this.resetPhotos();
|
|
} else {
|
|
this.purgeTask = results;
|
|
if (this.purgeTask.eta == -1) {
|
|
this.purgeTask.eta = 1000;
|
|
}
|
|
console.log("Task ETA: " + this.purgeTask.eta);
|
|
this.async(function() {
|
|
this.purgeStatus();
|
|
this.resetPhotos();
|
|
}, this.purgeTask.eta || 1000);
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
purgeTrashAction: function() {
|
|
var query = "";
|
|
if (this.mode == "trash") {
|
|
console.log("TODO: Prompt user 'Are you sure?' ?");
|
|
query = "?permanent=1";
|
|
}
|
|
window.fetch("api/v1/photos/" + query, function(error, xhr) {
|
|
if (error) {
|
|
console.log("Unable to take action on photo: " + error);
|
|
return;
|
|
}
|
|
|
|
var results;
|
|
try {
|
|
results = JSON.parse(xhr.responseText);
|
|
} catch (___) {
|
|
this.$.toast.text = "Unable to parse delete task.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse photos");
|
|
return;
|
|
}
|
|
|
|
if (results.task) {
|
|
this.purgeTask = results.task;
|
|
this.async(function() {
|
|
this.purgeStatus();
|
|
}, 250);
|
|
} else {
|
|
this.resetPhotos();
|
|
}
|
|
}.bind(this), {}, "DELETE");
|
|
},
|
|
|
|
deleteAction: function(thumbnail) {
|
|
thumbnail.disabled = true;
|
|
var query = "";
|
|
if (this.mode == "trash") {
|
|
console.log("TODO: Prompt user 'Are you sure?' ?");
|
|
query += "?permanent=1";
|
|
}
|
|
|
|
window.fetch("api/v1/photos/" + thumbnail.item.id + query, function(thumbnail, error) {
|
|
thumbnail.disabled = false;
|
|
|
|
if (error) {
|
|
console.log("Unable to take action on photo: " + error);
|
|
return;
|
|
}
|
|
|
|
/* If we are in the lightbox mode, then reload the corresponing
|
|
* thumb in the main view as well */
|
|
if (thumbnail.tagName == "PHOTO-LIGHTBOX") {
|
|
console.log("Delete from lightbox");
|
|
var index = this.findThumbnail(thumbnail.item.id);
|
|
if (index == -1) {
|
|
this.$.lightbox.close();
|
|
return;
|
|
}
|
|
thumbnail = this.thumbnails[index];
|
|
if (this.thumbnails.length == 1) {
|
|
this.$.lightbox.close();
|
|
} else {
|
|
this.lightBoxNext();
|
|
}
|
|
} else {
|
|
console.log("Delete from thumbnail");
|
|
}
|
|
|
|
this._removeImageAfterFetch(thumbnail, function() {
|
|
/* If there is a lightbox, close the lightbox */
|
|
/* In duplicates mode, if an image is deleted it has the same
|
|
* effect as renaming it since it removes it from the view */
|
|
if (this.mode != "duplicates") {
|
|
return;
|
|
}
|
|
/* If there is now only one item with this thumbnail's name,
|
|
* remove it from the view */
|
|
var orphan = null;
|
|
for (var i = 0; i < this.thumbnails.length; i++) {
|
|
if (this.thumbnails[i].item.filename == thumbnail.item.filename) {
|
|
if (orphan) {
|
|
return;
|
|
}
|
|
orphan = this.thumbnails[i];
|
|
}
|
|
}
|
|
if (orphan) {
|
|
this._removeImageAfterFetch(orphan);
|
|
}
|
|
}.bind(this));
|
|
}.bind(this, thumbnail), {}, "DELETE");
|
|
},
|
|
|
|
renameAction: function(thumbnail) {
|
|
thumbnail.disabled = true;
|
|
window.fetch("api/v1/photos/" + thumbnail.item.id + "?a=rename",
|
|
this._removeImageAfterFetch.bind(this, thumbnail, function() {
|
|
/* If there is now only one item with this thumbnail's name,
|
|
* remove it from the view */
|
|
var orphan = null;
|
|
for (var i = 0; i < this.thumbnails.length; i++) {
|
|
if (this.thumbnails[i].item.filename == thumbnail.item.filename) {
|
|
if (orphan) {
|
|
return;
|
|
}
|
|
orphan = this.thumbnails[i];
|
|
}
|
|
}
|
|
if (orphan) {
|
|
this._removeImageAfterFetch(orphan);
|
|
}
|
|
}.bind(this)), {}, "PUT");
|
|
},
|
|
|
|
rotateAction: function(thumbnail, direction) {
|
|
thumbnail.disabled = true;
|
|
thumbnail.loading = true;
|
|
|
|
window.fetch("api/v1/photos/" + thumbnail.item.id + "?a=rotate&direction=" + direction,
|
|
function(thumbnail, error) {
|
|
|
|
thumbnail.disabled = false;
|
|
thumbnail.loading = false;
|
|
|
|
if (error) {
|
|
console.log("Unable to take action on photo: " + error);
|
|
return;
|
|
}
|
|
|
|
thumbnail.reload();
|
|
|
|
/* If we are in the lightbox mode, then reload the corresponing
|
|
* thumb in the main view as well */
|
|
if (thumbnail.tagName == "PHOTO-LIGHTBOX") {
|
|
for (var i = 0; i < this.thumbnails.length; i++) {
|
|
if (this.thumbnails[i].item.id == thumbnail.item.id) {
|
|
this.thumbnails[i].reload();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}.bind(this, thumbnail), {}, "PUT");
|
|
},
|
|
|
|
_imageAction: function(event) {
|
|
switch (event.detail) {
|
|
case "undo": /* Undelete an image */
|
|
this.undoAction(event.currentTarget);
|
|
break;
|
|
|
|
case "delete": /* Delete image */
|
|
this.deleteAction(event.currentTarget);
|
|
break;
|
|
|
|
case "image:rotate-left":
|
|
this.rotateAction(event.currentTarget, "left");
|
|
break;
|
|
|
|
case "image:rotate-right":
|
|
this.rotateAction(event.currentTarget, "right");
|
|
break;
|
|
|
|
case "text-format": /* Rename this image */
|
|
this.renameAction(event.currentTarget);
|
|
break;
|
|
}
|
|
},
|
|
|
|
pathTapped: function(event) {
|
|
this.path = event.currentTarget.path;
|
|
Polymer.dom(this.$.thumbnails).innerHTML = "";
|
|
this._loadAlbums();
|
|
this._loadDays();
|
|
this._loadPhotos();
|
|
},
|
|
|
|
_loadPhotos: function(start, append, limit) {
|
|
if (this.mode == "login" || this.mode == "loading") {
|
|
return;
|
|
}
|
|
|
|
if (this.loading == true) {
|
|
return;
|
|
}
|
|
this.loading = true;
|
|
|
|
this.limit = limit || 100;
|
|
|
|
var params = {
|
|
limit: this.limit,
|
|
}, query = "";
|
|
if (start) {
|
|
params.next = start;
|
|
}
|
|
for (var key in params) {
|
|
if (query == "") {
|
|
query = "?";
|
|
} else {
|
|
query += "&";
|
|
}
|
|
query += key + "=" + encodeURIComponent(params[key]);
|
|
}
|
|
|
|
var path = this.path || "", mode = this.mode;
|
|
if (mode != "albums") {
|
|
path = mode;
|
|
if (mode == "time") {
|
|
path = "";
|
|
} else if (mode == "memories" || mode == "slideshow") {
|
|
path = "memories/" + (this.date.replace(this.year + "-", "") || "");
|
|
} else if (mode == "holiday") {
|
|
path = "holiday/" + this.holiday;
|
|
}
|
|
}
|
|
var username = this.user ? this.user.username : "";
|
|
console.log("Requesting " + this.limit + " photos from " + path);
|
|
window.fetch("api/v1/photos/" + path + query, function(path, error, xhr) {
|
|
this.loading = false;
|
|
|
|
if (!this.user) {
|
|
return;
|
|
}
|
|
|
|
if ((username != (this.user ? this.user.username : "")) ||
|
|
(mode != this.mode) ||
|
|
((mode == "albums") && (path != (this.path || ""))) ||
|
|
((mode == "holiday") && (path != ("holiday/" + this.holiday))) ||
|
|
((mode == "memories" || mode == "slideshow") && (path != ("memories/" + (this.date.replace(this.year + "-", "") || ""))))) {
|
|
console.log("Skipping results for old query. Triggering re-fetch of photos for new path or mode.");
|
|
this._loadPhotos();
|
|
return;
|
|
}
|
|
|
|
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(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
|
|
} else {
|
|
this.base = "/";
|
|
}
|
|
|
|
console.log(results.items.length + " photos received.");
|
|
|
|
this.appendItems(results.items);
|
|
|
|
if (results.more) {
|
|
this._loadPhotos(results.cursor, true, this.limit * 2);
|
|
}
|
|
|
|
if (this.mode == "holiday") {
|
|
this.holidayTitle = results.holiday;
|
|
}
|
|
}.bind(this, path));
|
|
},
|
|
|
|
_loadAlbums: function() {
|
|
if (this.mode == "login" || this.mode == "loading") {
|
|
return;
|
|
}
|
|
|
|
if (this.loadingAlbums == true) {
|
|
return;
|
|
}
|
|
this.loadingAlbums = true;
|
|
|
|
var path = this.path || "";
|
|
window.fetch("api/v1/albums/" + path, function(path, error, xhr) {
|
|
this.loadingAlbums = false;
|
|
|
|
if (!this.user) {
|
|
return;
|
|
}
|
|
|
|
if (path != (this.path || "")) {
|
|
console.log("Skipping results for old query. Triggering re-fetch of albums for new path.");
|
|
this._loadAlbums();
|
|
return;
|
|
}
|
|
|
|
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, path));
|
|
},
|
|
|
|
daysChanged: function(days) {
|
|
var grouped = {}, date;
|
|
/* Build an Object with properties for each MM-DD that contains photos */
|
|
days.forEach(function(day) {
|
|
date = day.date.replace(/[0-9]*-/, "");
|
|
if (!(date in grouped)) {
|
|
grouped[date] = parseInt(day.count) || 0;
|
|
} else {
|
|
grouped[date] += parseInt(day.count) || 0;
|
|
}
|
|
});
|
|
|
|
/* Build a dense array of the days identified in previous step */
|
|
var daysGrouped = [];
|
|
for (var key in grouped) {
|
|
daysGrouped.push({
|
|
date: key,
|
|
count: grouped[key]
|
|
});
|
|
}
|
|
this.daysGrouped = daysGrouped;
|
|
},
|
|
|
|
_loadDays: function() {
|
|
if (this.mode == "login" || this.mode == "loading") {
|
|
return;
|
|
}
|
|
|
|
if (this.loadingDays == true) {
|
|
return;
|
|
}
|
|
this.loadingDays = true;
|
|
|
|
var path = this.path || "";
|
|
window.fetch("api/v1/days/" + path, function(path, error, xhr) {
|
|
this.loadingDays = false;
|
|
|
|
if (!this.user) {
|
|
return;
|
|
}
|
|
|
|
if (path != (this.path || "")) {
|
|
console.log("Skipping results for old query. Triggering re-fetch of days for new path.");
|
|
this._loadDays();
|
|
return;
|
|
}
|
|
|
|
if (error) {
|
|
console.log("Error loading days: " + (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 days list.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.error("Unable to parse photos");
|
|
return;
|
|
}
|
|
|
|
this.days = results;
|
|
}.bind(this, path));
|
|
},
|
|
|
|
onResize: function(event) {
|
|
this.debounce("resize", function() {
|
|
this.triggerVisibilityChecks();
|
|
var width = 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);
|
|
},
|
|
|
|
setActions: function() {
|
|
if (this.user && this.user.maintainer) {
|
|
let actions = [ "delete" ];
|
|
|
|
if (this.mode == "duplicates") {
|
|
actions.unshift("text-format");
|
|
}
|
|
|
|
if (this.mode == "trash") {
|
|
actions.unshift("undo");
|
|
} else {
|
|
actions.unshift("image:rotate-right");
|
|
actions.unshift("image:rotate-left");
|
|
}
|
|
|
|
this.actions = actions;
|
|
} else {
|
|
this.actions = [];
|
|
}
|
|
},
|
|
|
|
loadHolidays: function() {
|
|
window.fetch("api/v1/holidays", function(error, xhr) {
|
|
if (error) {
|
|
console.error(JSON.stringify(error, null, 2));
|
|
return;
|
|
}
|
|
|
|
var results;
|
|
try {
|
|
results = JSON.parse(xhr.responseText);
|
|
} catch (___) {
|
|
this.$.toast.text = "Unable to parse holidays.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.log(xhr.responseText);
|
|
return;
|
|
}
|
|
|
|
this.holiday = results.next;
|
|
this.nextHoliday = results.next;
|
|
this.holidays = results.holidays;
|
|
Polymer.dom.flush();
|
|
this.highlightHoliday(this.holiday);
|
|
}.bind(this));
|
|
},
|
|
|
|
userChanged: function(user) {
|
|
if (!this.firstRequest) {
|
|
this.mode = "loading";
|
|
return;
|
|
}
|
|
|
|
this.resetPhotos();
|
|
|
|
if (!user) {
|
|
this.mode = "login";
|
|
this.loginStatus = null;
|
|
this.actions = [];
|
|
return;
|
|
}
|
|
|
|
this.username = user.username;
|
|
|
|
if (!user.restriction) {
|
|
this.loginStatus = null;
|
|
this.mode = "memories";
|
|
this.loadHolidays();
|
|
this.setActions();
|
|
return;
|
|
}
|
|
|
|
this.actions = [];
|
|
this.mode = "login";
|
|
|
|
this.loginStatusTitle = user.restriction;
|
|
if (!user.mailVerified) {
|
|
this.loginStatus = "An email has been sent to " + user.mail + ".\n\n" +
|
|
"Click the link in that email to verify your email address.";
|
|
} else if (!user.authenticated) {
|
|
this.loginStatus =
|
|
"The site admin needs to authorize your account before you can access the system.\n" +
|
|
"\n" +
|
|
"They have received an email and will process the request as quickly as possible.";
|
|
} else {
|
|
this.loginStatusTitle = "Access denied.";
|
|
this.loginStatus = user.restriction;
|
|
}
|
|
},
|
|
|
|
ready: function() {
|
|
|
|
var base = document.querySelector("base");
|
|
if (base) {
|
|
this.basePath = base.getAttribute("href");
|
|
} else {
|
|
this.basePath = "/";
|
|
}
|
|
|
|
this.resetPhotos();
|
|
|
|
this.$.calendar.partsHidden = {
|
|
"year": true
|
|
};
|
|
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);
|
|
|
|
this.loading = true;
|
|
this.firstRequest = false;
|
|
|
|
window.fetch("api/v1/users", function(error, xhr) {
|
|
this.firstRequest = true;
|
|
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 parse authentication response.";
|
|
this.$.toast.setAttribute("error", true);
|
|
this.$.toast.updateStyles();
|
|
this.$.toast.show();
|
|
console.log(xhr.responseText);
|
|
return;
|
|
}
|
|
|
|
if (results && results.username) {
|
|
this.user = results;
|
|
} else {
|
|
if (this.user) {
|
|
this.user = null;
|
|
} else {
|
|
this.userChanged(this.user);
|
|
}
|
|
}
|
|
}.bind(this));
|
|
|
|
this.onResize();
|
|
|
|
document.addEventListener("scroll", this.onScroll.bind(this));
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</dom-module>
|
|
</html>
|