osd.js 10.9 KB
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2015 Adobe Systems Incorporated
 * All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 */

/* globals */

define('screens/player/ui/osd/osd', [
    'jquery',
    'underscore',
    'screens/player/shared/serviceadmin',
    'screens/player/store/store',
    'screens/player/firmware/core/config/config',
    'screens/player/firmware/core/statusmodel/statusmodel'
], function($, _, ServiceAdmin, Store, Config, Statusmodel) {
    'use strict';

    /**
     * Default options for the component.
     *
     * @typedef {Object} OSD.OSDOptions
     * @type {Object}
     * @property {String} [baseCss]             The base CSS class for the component.
     * @property {String} [title]               Title for the OSD.
     * @property {Number} [longPressDuration]   Duration of a long press (in ms)
     */
    var DEFAULTS = {
        baseCss: 'aem-ScreensPlayer-osd',
        title: 'Screens Experience',
        longPressDuration: 1000,
        overlay: null,
        player: null
    };

    /**
     * Create the OSD dialog structure.
     *
     * @return {jQuery} The jQuery selector to the dialog element
     */
    var _buildOSD = function() {
        var $osd = $(document.createElement('div')).addClass(this.options.baseCss);
        $('body').append($osd);

        // .aem-ScreensPlayer-osd-version
        $osd.append($(document.createElement('h4')).addClass(this.options.baseCss + '-version'));

        // .aem-ScreensPlayer-osd-header
        var $header = $(document.createElement('div')).addClass(this.options.baseCss + '-header');
        $header.append($(document.createElement('h1')).text(this.options.title));
        $header.append($(document.createElement('button')).addClass(this.options.baseCss + '-reload'));
        $osd.append($header);

        // .aem-ScreensPlayer-osd-content
        var $content = $(document.createElement('div')).addClass(this.options.baseCss + '-content');
        $osd.append($content);

        // .aem-ScreensPlayer-osd-channelSwitcher
        $content.append($(document.createElement('div')).addClass(this.options.baseCss + '-channelSwitcher'));

        // .aem-ScreensPlayer-osd-footer
        var $footer = $(document.createElement('div')).addClass(this.options.baseCss + '-footer');
        $footer.append($(document.createElement('button')).addClass(this.options.baseCss + '-close'));
        $osd.append($footer);

        // bind events
        var player = this.options.player;
        var store = ServiceAdmin.getService(Store.serviceName);

        $osd.on('click touchend pointerup', '.' + this.options.baseCss + '-channelButton:not(.is-active):not(.is-cacheUnavailable)', function(ev) {
            ev.preventDefault();

            // do not hide directly, send hide action
            store.dispatch({type: 'osd-hide'}, {});
            player.emit(player.EVENTS.SWITCH_CHANNEL, ev.currentTarget.getAttribute('data-path'), true);
        });
        $osd.on('click touchend pointerup', '.' + this.options.baseCss + '-close', function(ev) {
            ev.preventDefault();
            store.dispatch({type: 'osd-hide'}, {});
        });
        $osd.on('click touchend pointerup', '.' + this.options.baseCss + '-reload', function(ev) {
            ev.preventDefault();
            // hide osd so that it does not appear on reload
            store.dispatch({type: 'osd-hide'}, {});
            player.reload();
        });

        return $osd;
    };

    function _updateOSDVersion(statusmodel) {
        var versionInfo = '';

        if (statusmodel) {
            versionInfo += 'version ' + statusmodel.version;
            var dim = statusmodel.deviceDimensions;
            versionInfo += ' - (' + dim.viewportWidth + 'x' + dim.viewportHeight + ')';
            versionInfo += ' / (' + dim.deviceWidth + 'x' + dim.deviceHeight + ')';
        }

        $('.' + this.options.baseCss + '-version').text(versionInfo);
    }

    /**
     * Create the trigger button that opens the OSD.
     *
     * @return {jQuery} The jQuery selector to the trigger button
     */
    var _buildOSDTrigger = function() {
        var $trigger = $(document.createElement('button')).addClass(this.options.baseCss + '-trigger');
        $('body').append($trigger);

        // Long-press on the button should toggle the OSD
        $trigger.on('mousedown touchstart pointerdown', function(ev) {
            ev.stopPropagation();
            if (this.options.enableHiddenTrigger && !this.longPressTimer) { // Don't react if disabled and avoid duplicate calls
                this.longPressTimer = setTimeout(function() {
                    clearTimeout(this.longPressTimer);
                    this.longPressTimer = null;
                    var store = ServiceAdmin.getService(Store.serviceName);
                    store.dispatch({type: 'osd-show'}, {});
                }.bind(this), this.options.longPressDuration);
            }
        }.bind(this));
        $trigger.on('mouseup touchend pointerup', function(ev) {
            if (this.longPressTimer) {
                clearTimeout(this.longPressTimer);
                this.longPressTimer = null;
            }
            ev.stopPropagation();
        }.bind(this));

        return $trigger;
    };

    /**
     * Add the buttons for each channel in the OSD.
     * @param {Object} state The channels state in the store
     */
    var _addChannelButtons = function(state) {
        var baseCss = this.options.baseCss;
        var currentChannel = state._current;

        var $content = this.$el.find('.' + baseCss + '-channelSwitcher');

        // Create a wrapper for the channel buttons
        var $wrapper = $content.find('.' + baseCss + '-channelList');
        if ($wrapper.length > 0) {
            $wrapper.remove();
        }
        $wrapper = $('<div/>').addClass(baseCss + '-channelList');
        $content.append($wrapper);

        // Add the channel buttons
        var channels = state._list;
        var $button;
        _.each(channels, function(channel) {
            $button = $('<button/>')
                .addClass(baseCss + '-channelButton')
                .attr('data-path', channel.path)
                .text(channel.title)
                .append($('<small/>')
                .attr('data-channel-name', channel.name)
                .text(channel.role));

            // Highlight the current channel
            if (currentChannel && currentChannel.role === channel.role) {
                $button.addClass('is-active').prop('disabled', true);
            }

            $wrapper.append($button);
        });
    };

    /**
     * On-Screen Display for the player.
     *
     * @class OSD
     *
     * @param {OSD.OSDOptions} [options]    Options for the component.
     */
    var OSD = function(options) {
        var self = this;

        this.options = $.extend({}, DEFAULTS, options);

        // Build the OSD
        this.$el = _buildOSD.call(this, options);
        this.hide();

        // Add the 'hidden' trigger button
        this.$trigger = _buildOSDTrigger.call(this);

        // listen to store changes
        var store = ServiceAdmin.getService(Store.serviceName);

        // show/hide
        this._osdChangeListener = store.subscribe(function(osd) {
            var visible = osd.visible;
            switch (visible) {
                case 'show': {
                    self._statusModelChangeListener = store.subscribe(_updateOSDVersion.bind(self), Statusmodel.NAMESPACE);
                    self._channelsConfigChangeListener = store.subscribe(_addChannelButtons.bind(self), Config.NAMESPACES.CHANNELS);
                    var state = store.getState();
                    _updateOSDVersion.call(self, state[Statusmodel.NAMESPACE]);
                    _addChannelButtons.call(self, state[Config.NAMESPACES.CHANNELS]);
                    self.show();
                    break;
                }
                default: {
                    self.hide();
                    if (self._statusModelChangeListener) {
                        store.unsubscribe(self._statusModelChangeListener);
                        self._statusModelChangeListener = null;
                    }
                    if (self._channelsConfigChangeListener) {
                        store.unsubscribe(self._channelsConfigChangeListener);
                        self._channelsConfigChangeListener = null;
                    }
                    break;
                }
            }
        }, 'osd');
    };

    /**
     * Show the tooltip.
     * @memberof OSD
     *
     * @param {Function} [cb] The function to call once the OSD is shown
     */
    OSD.prototype.show = function(cb) {
        var overlay = this.options.overlay;
        if (overlay) {
            overlay.$el.one('click pointerup touchend', function(ev) {
                ev.preventDefault();
                var store = ServiceAdmin.getService(Store.serviceName);
                store.dispatch({type: 'osd-hide'}, {});
            });
            overlay.show(cb);
        }
        else {
            console.warn('Overlay module not loaded');
        }

        this.$el.removeClass('is-hidden').addClass('is-visible');
    };

    /**
     * Hide the OSD.
     * @memberof OSD
     *
     * @param {Function} [cb] The function to call once the OSD is hidden
     */
    OSD.prototype.hide = function(cb) {
        if (!this.$el) {
            return;
        }

        this.$el.one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(ev) {
            if (!$(ev.target).hasClass('is-visible')) {
                this.$el.off('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend');
                $.isFunction(cb) && cb();
            }
        }.bind(this));
        this.$el.removeClass('is-visible').addClass('is-hidden');
        if (this.options.overlay) {
            this.options.overlay.hide();
        }
        else {
            console.warn('Overlay module not loaded');
        }

    };

    /**
     * Destroy the OSD.
     * @memberof OSD
     */
    OSD.prototype.destroy = function() {
        if (this.longPressTimer) {
            clearTimeout(this.longPressTimer);
            this.longPressTimer = null;
        }

        var store = ServiceAdmin.getService(Store.serviceName);
        if (this._osdChangeListener) {
            store.unsubscribe(this._osdChangeListener);
            this._osdChangeListener = null;
        }

        this.$trigger.remove();
        this.$trigger = null;

        this.$el.remove();
        this.$el = null;
    };

    return OSD;

});