Source: options/options.js

/*
 *  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';

  /**
   * Extension's Options page
   * @namespace Options
   */

  new ExceptionHandler();

  /**
   * Manage an html page that is inserted on demand<br />
   * May also be a url link to external site
   * @typedef {{}} Options.Page
   * @property {string} label - label for Nav menu
   * @property {string} route - element name route to page
   * @property {string} icon - icon for Nav Menu
   * @property {?Object|Function} obj - something to be done when selected
   * @property {boolean} ready - true if html is inserted
   * @property {boolean} divider - true for divider before item
   * @memberOf Options
   */

  /**
   * Path to the extension in the Web Store
   * @type {string}
   * @const
   * @private
   * @memberOf Options
   */
  const EXT_URI =
      'https://chrome.google.com/webstore/detail/photo-screen-saver/' +
      chrome.runtime.id + '/';

  /**
   * Path to my Pushy Clipboard extension
   * @type {string}
   * @const
   * @default
   * @private
   * @memberOf Options
   */
  const PUSHY_URI =
      'https://chrome.google.com/webstore/detail/pushy-clipboard/' +
      'jemdfhaheennfkehopbpkephjlednffd';

  /**
   * auto-binding template
   * @type {Object}
   * @const
   * @private
   * @memberOf Options
   */
  const t = document.querySelector('#t');

  /**
   * Array of pages
   * @type {Options.Page[]}
   * @memberOf Options
   */
  t.pages = [
    {
      label: Chrome.Locale.localize('menu_settings'), route: 'page-settings',
      icon: 'myicons:settings', obj: null, ready: true, divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_google'),
      route: 'page-google-photos', icon: 'myicons:cloud',
      obj: _showGooglePhotosPage, ready: false, divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_preview'), route: 'page-preview',
      icon: 'myicons:pageview', obj: _showScreensaverPreview, ready: true,
      divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_error'), route: 'page-error',
      icon: 'myicons:error', obj: _showErrorPage,
      ready: false, disabled: false, divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_help'), route: 'page-help',
      icon: 'myicons:help', obj: _showHelpPage, ready: false,
      divider: true,
    },
    {
      label: Chrome.Locale.localize('help_faq'), route: 'page-faq',
      icon: 'myicons:help',
      obj: 'https://opus1269.github.io/photo-screen-saver/faq.html',
      ready: true, divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_support'), route: 'page-support',
      icon: 'myicons:help', obj: `${EXT_URI}support`, ready: true,
      divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_rate'), route: 'page-rate',
      icon: 'myicons:grade', obj: `${EXT_URI}reviews`, ready: true,
      divider: false,
    },
    {
      label: Chrome.Locale.localize('menu_pushy'), route: 'page-pushy',
      icon: 'myicons:extension', obj: PUSHY_URI, ready: true,
      divider: true,
    },
  ];

  // Error dialog
  t.dialogTitle = '';
  t.dialogText = '';

  /**
   * Current {@link Options.Page}
   * @type {string}
   * @memberOf Options
   */
  t.route = 'page-settings';

  /**
   * Event Listener for template bound event to know when bindings
   * have resolved and content has been stamped to the page
   * @memberOf Options
   */
  t.addEventListener('dom-change', function() {
    Chrome.GA.page('/options.html');

    // listen for chrome messages
    Chrome.Msg.listen(_onMessage);

    // initialize lastError enabled state
    _setErrorMenuState();
    
    // listen for changes to chrome.storage
    chrome.storage.onChanged.addListener(function(changes) {
      for (const key in changes) {
        if (changes.hasOwnProperty(key)) {
          if (key === 'lastError') {
            _setErrorMenuState();
            break;
          }
        }
      }
    });
  });

  /**
   * Event: navigation menu selected
   * Route to proper page
   * @param {Event} event - ClickEvent
   * @memberOf Options
   */
  t._onNavMenuItemTapped = function(event) {
    // Close drawer after menu item is selected if it is narrow
    const drawerPanel = document.querySelector('#paperDrawerPanel');
    if (drawerPanel && drawerPanel.narrow) {
      drawerPanel.closeDrawer();
    }
    const idx = _getPageIdx(event.currentTarget.id);

    Chrome.GA.event(Chrome.GA.EVENT.MENU, t.pages[idx].route);

    const prevRoute = t.route;

    if (!t.pages[idx].obj) {
      // some pages are just pages
      t.route = t.pages[idx].route;
      _scrollPageToTop();
    } else if (typeof t.pages[idx].obj === 'string') {
      // some pages are url links
      t.$.mainMenu.select(prevRoute);
      chrome.tabs.create({url: t.pages[idx].obj});
    } else {
      // some pages have functions to view them
      t.pages[idx].obj(idx, prevRoute);
    }
  };

  /**
   * Computed property: Page title
   * @returns {string} i18n title
   * @memberOf Options
   */
  t._computeTitle = function() {
    return Chrome.Locale.localize('chrome_extension_name');
  };

  /**
   * Computed property: Menu label
   * @returns {string} i18n label
   * @memberOf Options
   */
  t._computeMenu = function() {
    return Chrome.Locale.localize('menu');
  };

  /**
   * Get the index into the {@link Options.pages} array
   * @param {string} name - {@link Options.page} route
   * @returns {int} index into array
   * @private
   * @memberOf Options
   */
  function _getPageIdx(name) {
    return t.pages.map(function(e) {
      return e.route;
    }).indexOf(name);
  }

  /**
   * Show the Google Photos page
   * @param {int} index - index into [t.pages]{@link Options.t.pages}
   * @memberOf Options
   */
  function _showGooglePhotosPage(index) {
    if (!t.pages[index].ready) {
      // create the page the first time
      t.pages[index].ready = true;
      t.gPhotosPage =
          new app.GooglePhotosPage('gPhotosPage');
      Polymer.dom(t.$.googlePhotosInsertion).appendChild(t.gPhotosPage);
    } else if (Chrome.Storage.getBool('isAlbumMode')) {
      t.gPhotosPage.loadAlbumList();
    }
    t.route = t.pages[index].route;
    _scrollPageToTop();
  }

  /**
   * Show the error viewer page
   * @param {int} index - index into {@link Options.pages}
   * @private
   * @memberOf Options
   */
  function _showErrorPage(index) {
    if (!t.pages[index].ready) {
      // insert the page the first time
      t.pages[index].ready = true;
      const el = new app.ErrorPageFactory();
      Polymer.dom(t.$.errorInsertion).appendChild(el);
    }
    t.route = t.pages[index].route;
    _scrollPageToTop();
  }

  /**
   * Show the help page
   * @param {int} index - index into [t.pages]{@link Options.t.pages}
   * @private
   * @memberOf Options
   */
  function _showHelpPage(index) {
    if (!t.pages[index].ready) {
      // insert the page the first time
      t.pages[index].ready = true;
      const el = new app.HelpPageFactory();
      Polymer.dom(t.$.helpInsertion).appendChild(el);
    }
    t.route = t.pages[index].route;
    _scrollPageToTop();
  }

  // noinspection JSUnusedLocalSymbols
  /**
   * Display a preview of the screen saver
   * @param {int} index - index into [t.pages]{@link Options.t.pages}
   * @param {string} prevRoute - last page selected
   * @memberOf Options
   */
  function _showScreensaverPreview(index, prevRoute) {
    // reselect previous page - need to delay so tap event is done
    t.async(function() {
      t.$.mainMenu.select(prevRoute);
    }, 500);
    Chrome.Msg.send(app.Msg.SS_SHOW).catch(() => {});
  }

  /**
   * Scroll page to top
   * @memberOf Options
   */
  function _scrollPageToTop() {
    t.$.scrollPanel.scrollToTop(true);
  }

  /**
   * Set enabled state of Error Viewer menu item
   * @memberOf Options
   */
  function _setErrorMenuState() {
    // disable error-page if no lastError
    Chrome.Storage.getLastError().then((lastError) => {
      const idx = _getPageIdx('page-error');
      const el = document.getElementById(t.pages[idx].route);
      if (el && !Chrome.Utils.isWhiteSpace(lastError.message)) {
        el.removeAttribute('disabled');
      } else if (el) {
        el.setAttribute('disabled', 'true');
      }
      return Promise.resolve();
    }).catch((err) => {
      Chrome.GA.error(err.message, 'Options._setErrorMenuState');
    });
  }

  // noinspection JSUnusedLocalSymbols
  /**
   * Event: Fired when a message is sent from either an extension process<br>
   * (by runtime.sendMessage) or a content script (by tabs.sendMessage).
   * @see https://developer.chrome.com/extensions/runtime#event-onMessage
   * @param {Chrome.Msg.Message} request - details for the message
   * @param {Object} [sender] - MessageSender object
   * @param {Function} [response] - function to call once after processing
   * @returns {boolean} true if asynchronous
   * @private
   * @memberOf Options
   */
  function _onMessage(request, sender, response) {
    if (request.message === Chrome.Msg.HIGHLIGHT.message) {
      // highlight ourselves and let the sender know we are here
      const chromep = new ChromePromise();
      chromep.tabs.getCurrent().then((t) => {
        chrome.tabs.update(t.id, {'highlighted': true});
        return null;
      }).catch((err) => {
        Chrome.Log.error(err.message, 'chromep.tabs.getCurrent');
      });
      response(JSON.stringify({message: 'OK'}));
    } else if (request.message === Chrome.Msg.STORAGE_EXCEEDED.message) {
      // Display Error Dialog if a save action exceeded the
      // localStorage limit
      t.dialogTitle = Chrome.Locale.localize('err_storage_title');
      t.dialogText = Chrome.Locale.localize('err_storage_desc');
      t.$.errorDialog.open();
    } else if (request.message === app.Msg.PHOTO_SOURCE_FAILED.message) {
      // failed to load
      t.$.settingsPage.deselectPhotoSource(request.key);
      t.dialogTitle = Chrome.Locale.localize('err_photo_source_title');
      t.dialogText = request.error;
      t.$.errorDialog.open();
    }
    return false;
  }
})();