/*
* Copyright (c) 2015-2017, Michael A. Updike All rights reserved.
* Licensed under the BSD-3-Clause
* https://opensource.org/licenses/BSD-3-Clause
* https://github.com/opus1269/photo-screen-saver/blob/master/LICENSE.md
*/
(function() {
'use strict';
window.app = window.app || {};
new ExceptionHandler();
/**
* Aspect ratio of screen
* @type {number}
* @const
* @private
* @memberOf app.SSView
*/
const _SCREEN_ASP = screen.width / screen.height;
/**
* Screensaver zoom view and base class for other SSView classes
* @property {Element} image - paper-image
* @property {Element} author - label
* @property {Element} time - label
* @property {Element} location - Geo location
* @property {Object} model - template item model
* @property {string} url - photo url, binding
* @property {string} authorLabel - author text, binding
* @property {string} locationLabel - location text, binding
* @alias app.SSView
*/
app.SSView = class SSView {
/**
* Create a new SSView
* @param {app.SSPhoto} photo - An {@link app.SSPhoto}
* @constructor
*/
constructor(photo) {
this.photo = photo;
this.image = null;
this.author = null;
this.time = null;
this.location = null;
this.model = null;
this.url = photo.getUrl();
this.authorLabel = '';
this.locationLabel = '';
}
/**
* Factory Method to create a new {@link app.SSView}
* @param {app.SSPhoto} photo - An {@link app.SSPhoto}
* @param {app.SSViews.Type} sizing - photo sizing type
* @returns {app.SSView} a new SSView or subclass
* @static
*/
static createView(photo, sizing) {
switch (sizing) {
case app.SSViews.Type.LETTERBOX:
return new app.SSViewLetterbox(photo);
case app.SSViews.Type.ZOOM:
return new app.SSView(photo);
case app.SSViews.Type.FRAME:
return new app.SSViewFrame(photo);
case app.SSViews.Type.FULL:
return new app.SSViewFull(photo);
default:
Chrome.Log.error(`Bad SSView type: ${sizing}`, 'SSView.createView');
return new app.SSViewLetterbox(photo);
}
}
/**
* Determine if a photo would look bad zoomed or stretched on the screen
* @param {number} asp - an aspect ratio
* @returns {boolean} true if a photo aspect ratio differs substantially
* from the screens'
* @private
*/
static _isBadAspect(asp) {
// arbitrary
const CUT_OFF = 0.5;
return (asp < _SCREEN_ASP - CUT_OFF) || (asp > _SCREEN_ASP + CUT_OFF);
}
/**
* Determine if a given aspect ratio should be ignored
* @param {number} asp - an aspect ratio
* @param {int} photoSizing - the sizing type
* @returns {boolean} true if the aspect ratio should be ignored
*/
static ignore(asp, photoSizing) {
let ret = false;
const skip = Chrome.Storage.getBool('skip');
if ((!asp || isNaN(asp)) ||
(skip && ((photoSizing === 1) || (photoSizing === 3)) &&
app.SSView._isBadAspect(asp))) {
// ignore photos that don't have aspect ratio
// or would look bad with cropped or stretched sizing options
ret = true;
}
return ret;
}
/**
* Should we show the location, if available
* @returns {boolean} true if we should show the location
* @static
*/
static _showLocation() {
return Chrome.Storage.getBool('showLocation');
}
/**
* Should we show the time
* @returns {boolean} true if we should show the time
* @static
*/
static showTime() {
return Chrome.Storage.getBool('showTime');
}
/**
* Does a photo have an author label to show
* @returns {boolean} true if we should show the author
*/
_hasAuthor() {
const photographer = this.photo.getPhotographer();
return !Chrome.Utils.isWhiteSpace(photographer);
}
/**
* Does a view have an author label set
* @returns {boolean} true if author label is not empty
*/
_hasAuthorLabel() {
return !Chrome.Utils.isWhiteSpace(this.authorLabel);
}
/**
* Does a photo have a geolocation
* @returns {boolean} true if geolocation point is non-null
*/
_hasLocation() {
return !!this.photo.getPoint();
}
/**
* Does a view have an location label set
* @returns {boolean} true if location label is not empty
*/
_hasLocationLabel() {
return !Chrome.Utils.isWhiteSpace(this.locationLabel);
}
/**
* Add superscript to the label for 500px photos
* @private
*/
_super500px() {
const type = this.photo.getType();
const authorText = this.authorLabel;
const sup = this.author.querySelector('#sup');
sup.textContent = '';
if (!Chrome.Utils.isWhiteSpace(authorText) && (type === '500')) {
sup.textContent = 'px';
}
}
/**
* Set the style for the time label
*/
_setTimeStyle() {
if (Chrome.Storage.getBool('largeTime')) {
this.time.style.fontSize = '8.5vh';
this.time.style.fontWeight = 300;
}
}
/**
* Set the url
*/
_setUrl() {
this.url = this.photo.getUrl();
this.model.set('view.url', this.url);
}
/**
* Set the author text
*/
_setAuthorLabel() {
this.authorLabel = '';
this.model.set('view.authorLabel', this.authorLabel);
this._super500px();
const type = this.photo.getType();
const photographer = this.photo.getPhotographer();
let newType = type;
const idx = type.search('User');
if (!Chrome.Storage.getBool('showPhotog') && (idx !== -1)) {
// don't show label for user's own photos, if requested
return;
}
if (idx !== -1) {
// strip off 'User'
newType = type.substring(0, idx - 1);
}
if (this._hasAuthor()) {
this.authorLabel = `${photographer} / ${newType}`;
} else {
// no photographer name
this.authorLabel = `${Chrome.Locale.localize('photo_from')} ${newType}`;
}
this.model.set('view.authorLabel', this.authorLabel);
this._super500px();
}
/**
* Set the geolocation text
*/
_setLocationLabel() {
this.locationLabel = '';
this.model.set('view.locationLabel', this.locationLabel);
if (app.SSView._showLocation() && this._hasLocation()) {
const point = this.photo.getPoint();
app.Geo.get(point).then((location) => {
if (location && this.model) {
location = location.replace('Unnamed Road, ', '');
this.locationLabel = location;
this.model.set('view.locationLabel', this.locationLabel);
}
return Promise.resolve();
}).catch((err) => {
const networkErr = Chrome.Locale.localize('err_network');
if (!err.message.includes(networkErr)) {
Chrome.GA.error(`${err.message}, point: ${point}`,
'SSView._setLocationLabel');
}
});
}
}
/**
* Set the elements of the view
* @param {Element} image - paper-image, photo
* @param {Element} author - div, photographer
* @param {Element} time - div, current time
* @param {Element} location - div, geolocation text
* @param {Object} model - template item model
*/
setElements(image, author, time, location, model) {
this.image = image;
this.author = author;
this.time = time;
this.location = location;
this.model = model;
this._setTimeStyle();
this.setPhoto(this.photo);
}
/**
* Set the photo
* @param {app.SSPhoto} photo - a photo to render
*/
setPhoto(photo) {
this.photo = photo;
this._setUrl();
this._setAuthorLabel(false);
this._setLocationLabel();
}
/**
* Render the page for display - the default CSS is for our view
* subclasses override this to determine the look of photo
*/
render() {}
/**
* Determine if a photo failed to load (usually 404 error)
* @returns {boolean} true if image load failed
*/
isError() {
return !this.image || this.image.error;
}
/**
* Determine if a photo has finished loading
* @returns {boolean} true if image is loaded
*/
isLoaded() {
return !!this.image && this.image.loaded;
}
};
})();