/* * 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(); /** * A photo from a {@link app.PhotoSource} * This is the photo information that is persisted. * * @typedef {{}} app.PhotoSource.Photo * @property {string} url - The url to the photo * @property {string} author - The photographer * @property {number} asp - The aspect ratio of the photo * @property {Object} [ex] - Additional information about the photo * @property {string} [point] - geolocation 'lat lon' */ /** * The photos for a {@link app.PhotoSource} * * @typedef {{}} app.PhotoSource.Photos * @property {string} type - type of {@link app.PhotoSource} * @property {app.PhotoSource.Photo[]} photos - The photos */ /** * A potential source of photos for the screen saver * @alias app.PhotoSource */ app.PhotoSource = class PhotoSource { /** * Create a new photo source * @param {string} useKey - The key for if the source is selected * @param {string} photosKey - The key for the collection of photos * @param {string} type - A descriptor of the photo source * @param {string} desc - A human readable description of the source * @param {boolean} isDaily - Should the source be updated daily * @param {boolean} isArray - Is the source an Array of photo Arrays * @param {?Object} [loadArg=null] - optional arg for load function * @constructor */ constructor(useKey, photosKey, type, desc, isDaily, isArray, loadArg = null) { this._useKey = useKey; this._photosKey = photosKey; this._type = type; this._desc = desc; this._isDaily = isDaily; this._isArray = isArray; this._loadArg = loadArg; // set the user facing description } /** * Factory Method to create a new {@link app.PhotoSource} * @param {app.PhotoSources.UseKey} useKey - photo source useKey * @returns {?app.PhotoSource} a new PhotoSource or subclass * @static */ static createSource(useKey) { switch (useKey) { case app.PhotoSources.UseKey.ALBUMS_GOOGLE: return new app.GoogleSource(useKey, 'albumSelections', 'Google User', Chrome.Locale.localize('google_title_photos'), true, true, true); case app.PhotoSources.UseKey.PHOTOS_GOOGLE: // not implemented yet return new app.GoogleSource(useKey, 'googleImages', 'Google User', 'NOT IMPLEMENTED', true, false, false); case app.PhotoSources.UseKey.CHROMECAST: return new app.CCSource(useKey, 'ccImages', 'Google', Chrome.Locale.localize('setting_chromecast'), false, false, null); case app.PhotoSources.UseKey.ED_500: return new app.Px500Source(useKey, 'editors500pxImages', '500', Chrome.Locale.localize('setting_500editors'), true, false, 'editors'); case app.PhotoSources.UseKey.POP_500: return new app.Px500Source(useKey, 'popular500pxImages', '500', Chrome.Locale.localize('setting_500popular'), true, false, 'popular'); case app.PhotoSources.UseKey.YEST_500: return new app.Px500Source(useKey, 'yesterday500pxImages', '500', Chrome.Locale.localize('setting_500yest'), true, false, 'fresh_yesterday'); case app.PhotoSources.UseKey.INT_FLICKR: return new app.FlickrSource(useKey, 'flickrInterestingImages', 'flickr', Chrome.Locale.localize('setting_flickr_int'), true, false, false); case app.PhotoSources.UseKey.AUTHOR: return new app.FlickrSource(useKey, 'authorImages', 'flickr', Chrome.Locale.localize('setting_mine'), false, false, true); case app.PhotoSources.UseKey.SPACE_RED: return new app.RedditSource(useKey, 'spaceRedditImages', 'reddit', Chrome.Locale.localize('setting_reddit_space'), true, false, 'r/spaceporn/'); case app.PhotoSources.UseKey.EARTH_RED: return new app.RedditSource(useKey, 'earthRedditImages', 'reddit', Chrome.Locale.localize('setting_reddit_earth'), true, false, 'r/EarthPorn/'); case app.PhotoSources.UseKey.ANIMAL_RED: return new app.RedditSource(useKey, 'animalRedditImages', 'reddit', Chrome.Locale.localize('setting_reddit_animal'), true, false, 'r/animalporn/'); default: // TODO title Chrome.Log.error(`Bad PhotoSource type: ${useKey}`, 'SSView.createView'); return null; } } /** * Add a {@link app.PhotoSource.Photo} to an existing Array * @param {Array} photos - {@link app.PhotoSource.Photo} Array * @param {string} url - The url to the photo * @param {string} author - The photographer * @param {number} asp - The aspect ratio of the photo * @param {Object} [ex] - Additional information about the photo * @param {string} [point] - 'lat lon' */ static addPhoto(photos, url, author, asp, ex, point) { /** @type {app.PhotoSource.Photo} */ const photo = { url: url, author: author, asp: asp.toPrecision(3), }; if (ex) { photo.ex = ex; } if (point) { photo.point = point; } photos.push(photo); } /** * Create a geo point string from a latitude and longitude * @param {number} lat - latitude * @param {number} lon - longitude * @returns {string} 'lat lon' * @memberOf app.Geo */ static createPoint(lat, lon) { if ((typeof lat === 'number') && (typeof lon === 'number')) { return `${lat.toFixed(6)} ${lon.toFixed(6)}`; } else { return `${lat} ${lon}`; } } /** * Fetch the photos for this source - override * @abstract * @returns {Promise<app.PhotoSource.Photo[]>} Array of photos */ fetchPhotos() { } /** * Get if we should update daily * @returns {boolean} if true, update daily */ isDaily() { return this._isDaily; } /** * Get the photos from local storage * @returns {app.PhotoSource.Photos} the photos */ getPhotos() { let ret = { type: this._type, photos: [], }; if (this.use()) { let photos = []; if (this._isArray) { let items = Chrome.Storage.get(this._photosKey); // could be that items have not been retrieved yet items = items || []; for (const item of items) { photos = photos.concat(item.photos); } } else { photos = Chrome.Storage.get(this._photosKey); // could be that items have not been retrieved yet photos = photos || []; } ret.photos = photos; } return ret; } /** * Determine if this source has been selected for display * @returns {boolean} true if selected */ use() { return Chrome.Storage.getBool(this._useKey); } /** * Process the photo source. * @returns {Promise<void>} void */ process() { if (this.use()) { return this.fetchPhotos().then((photos) => { const errMess = this._savePhotos(photos); if (!Chrome.Utils.isWhiteSpace(errMess)) { return Promise.reject(new Error(errMess)); } return Promise.resolve(); }).catch((err) => { let title = Chrome.Locale.localize('err_photo_source_title'); title += `: ${this._desc}`; Chrome.Log.error(err.message, 'PhotoSource.process', title); return Promise.reject(err); }); } else { localStorage.removeItem(this._photosKey); return Promise.resolve(); } } /** * Save the photos to localStorage in a safe manner * @param {app.PhotoSource.Photo[]} photos * - {@link app.PhotoSource.Photo} Array * @returns {?string} non-null on error * @private */ _savePhotos(photos) { let ret = null; const keyBool = this._useKey; if (photos && photos.length) { const set = Chrome.Storage.safeSet(this._photosKey, photos, keyBool); if (!set) { ret = 'Exceeded storage capacity.'; } } return ret; } }; })();