nbsrc/provider/MediaProvider.js

goog.module('nbsrc.provider.MediaProvider');

const Completer = goog.require('clulib.async.Completer');

const XhrIo = goog.require('goog.net.XhrIo');
const ResponseType = goog.require('goog.net.XhrIo.ResponseType');
const {listen} = goog.require('goog.events');
const ImageLoader = goog.require('goog.net.ImageLoader');
const EventTarget = goog.require('goog.events.EventTarget');
const EventType = goog.require('goog.events.EventType');
const NetEventType = goog.require('goog.net.EventType');

/**
 * When the window.devicePixelRatio is greater than 1 use retina images.
 * @type {boolean}
 * @const
 */
const useRetinaImages = window.devicePixelRatio > 1;

/**
 * This provider (singleton) takes care of loading, parsing and providing external media content (image, json).
 * @extends {EventTarget}
 */
class MediaProvider extends EventTarget
{
    constructor()
    {
        super();

        /**
         * Defines if all load promises should be stored in the source list.
         * @type {boolean}
         * @private
         */
        this.autoStoreSources_ = true;

        /**
         * List of stored loading promises, where key ist the file path.
         * @type {Map<string,Promise>}
         * @private
         */
        this.sources_ = new Map();

        /**
         * Relative default asset path.
         * @type {string}
         * @private
         */
        this.mediaAssetPath_ = 'asset/';

        /**
         * It is an absolute asset URL if the variable `window.ASSET_URL` is set, otherwise it is the relative {@Link MediaProvider#mediaAssetPath_}.
         * @type {string}
         * @private
         */
        this.mediaAssetUrl_ = window['ASSETS_URL'] ? window['ASSETS_URL'] + this.mediaAssetPath_ : this.mediaAssetPath_;
    }

    /**
     * Loads a media source and returns a promise that is fulfilled when the source is loaded.
     * @param {Array<string>|string} source path
     * @param {string=} additionalPathSegment additional path segment between {@Link MediaProvider#mediaAssetPath_} and the given source.
     * @param {string|null=} loadType Loading type like json or blob. If nothing is specified, the file extension is analyzed to determine the loading method.
     * @param {boolean=} storeSource Store loading promise in the {@Link MediaProvider#sources_} list.
     * @param {boolean=} preventCDNService Prevent loading over a cdn url, which is part of {@Link MediaProvider#mediaAssetUrl_}. Loading path will be relative stating with {@Link MediaProvider#mediaAssetPath_}.
     * @return {Promise}
     */
    load(source, additionalPathSegment = '', loadType = null, storeSource = false, preventCDNService = false)
    {
        let promises = [];

        if(typeof source == 'string')
        {
            let fileType = source.split('.').pop();
            if(additionalPathSegment.length > 0 && additionalPathSegment.substr(additionalPathSegment.length-1) != '/')
                additionalPathSegment+='/';
            let url = additionalPathSegment+source;

            if(this.sources_.has(url))
                return this.sources_.get(url);

            let responseType = this.getResponseType_(loadType);
            loadType = loadType || fileType;
            let promise = Promise.resolve();
            switch(loadType)
            {
                case 'json':
                    promise = this.loadJson_(url, preventCDNService);
                    break;
                case 'svg':
                case 'png':
                case 'jpg':
                case 'gif':
                    promise = this.loadImage_(url, preventCDNService);
                    break;
                default:
                    promise = this.load_(url, responseType, preventCDNService);
                    break;
            }

            if(storeSource === true || this.autoStoreSources_ === true)
                this.sources_.set(url, promise);

            return promise;
        }
        else if(Array.isArray(source))
        {
            source.forEach((url) => {
                promises.push(this.load(url, additionalPathSegment, loadType, storeSource, preventCDNService));
            })
        }

        return Promise.all(promises);
    }

    /**
     * Gets the response type by the given load type
     * @param loadType
     * @return {string}
     * @private
     */
    getResponseType_(loadType)
    {
        if(loadType == 'blob')
            return ResponseType.BLOB;
        else
            return ResponseType.DEFAULT;
    }

    /**
     * Loading an image and returns a promise that is fulfilled when the source is loaded.
     * @param {string} url
     * @param {boolean=} preventCDNService Prevent loading over a cdn url, which is part of {@Link MediaProvider#mediaAssetUrl_}. Loading path will be relative stating with {@Link MediaProvider#mediaAssetPath_}.
     * @return {Promise}
     * @private
     */
    loadImage_(url, preventCDNService = false)
    {
        let completer = new Completer();
        let imageLoader = new ImageLoader();
        let sourcePath = preventCDNService ? this.mediaAssetPath_ + url : this.mediaAssetUrl_ + url;
        imageLoader.addImage(url, sourcePath);

        listen(imageLoader, EventType.LOAD, (event) => {
            completer.resolve(event.target);
        }, false, this);

        listen(imageLoader, EventType.ERROR, () => {
            completer.reject('Couldn\'t load source ' + url + '.');
        }, false, this);

        imageLoader.start();

        return completer.getPromise();
    }

    /**
     * Loading a json and parse the result as a json object. Returns a promise that is fulfilled when the source is loaded.
     * @param {string} url Source path
     * @param {boolean=} preventCDNService Prevent loading over a cdn url, which is part of {@Link MediaProvider#mediaAssetUrl_}. Loading path will be relative stating with {@Link MediaProvider#mediaAssetPath_}.
     * @return {Promise}
     * @private
     */
    loadJson_(url, preventCDNService = false)
    {
        return this.load_(url, ResponseType.DEFAULT, preventCDNService).then((result) => {
            return JSON.parse(result);
        });
    }

    /**
     * Loading a source and returns a promise that is fulfilled when the source is loaded.
     * @param {string} url Source path
     * @param {string=} responseType Optional xhr response type
     * @param {boolean=} preventCDNService Prevent loading over a cdn url, which is part of {@Link MediaProvider#mediaAssetUrl_}. Loading path will be relative stating with {@Link MediaProvider#mediaAssetPath_}.
     * @return {Promise}
     * @private
     */
    load_(url, responseType = '', preventCDNService = false)
    {
        let completer = new Completer();
        let request = new XhrIo();
        request.setResponseType(/** @type {ResponseType} */ (responseType));

        listen(request, NetEventType.SUCCESS, (event) => {
            completer.resolve(event.currentTarget.getResponse());
        }, false, this);

        listen(request, EventType.ERROR, () => {
            completer.reject('Couldn\'t load source ' + url + '.');
        }, false, this);

        let sourcePath = preventCDNService ? this.mediaAssetPath_ + url : this.mediaAssetUrl_ + url;
        request.send(sourcePath);

        return completer.getPromise();
    }

    /**
     * Retrieve a source by key (file path). If the source has not been loaded yet, load it.
     * @param {string} key
     * @return {Promise}
     */
    getSource(key)
    {
        if(this.sources_.has(key))
        {
            return this.sources_.get(key);
        }
        else if(this.autoStoreSources_ === true)
        {
            return this.load(key);
        }

        return Promise.reject('No source for '+key+' was loaded!');
    }

    /**
     * Setter
     * @param {boolean} autoStoreSources
     */
    set autoStoreSources(autoStoreSources)
    {
        this.autoStoreSources_ = autoStoreSources;
    }

    /**
     * Getter
     * @return {boolean}
     */
    get autoStoreSources()
    {
        return this.autoStoreSources_;
    }

    /**
     * Setter
     * @param {string} mediaAssetPath
     */
    set mediaAssetPath(mediaAssetPath)
    {
        this.mediaAssetPath_ = mediaAssetPath;
        this.mediaAssetUrl_ = window['ASSETS_URL'] ? window['ASSETS_URL']+mediaAssetPath : mediaAssetPath;
    }

    /**
     * Getter
     * @return {string}
     */
    get mediaAssetPath()
    {
        return this.mediaAssetPath_;
    }

    /**
     * Getter
     * @return {string}
     */
    get mediaAssetUrl()
    {
        return this.mediaAssetUrl_;
    }
}

goog.addSingletonGetter(MediaProvider);

exports = {MediaProvider, useRetinaImages};