<commit_before>(function () {
    'use strict';

    const TABS_ASSETS = ['WAVES', 'BTC'];
    // Other gateways are added dynamically in the code below.
    const DROP_DOWN_ASSETS = ['ETH', 'BCH', 'LTC', 'USD', 'EUR'];

    /**
     * @param Base
     * @param {Waves} waves
     * @param user
     * @param {$rootScope.Scope} $scope
     * @param {$state} $state
     * @param {$location} $location
     * @param {app.utils.decorators} decorators
     * @param gateways
     * @param sepaGateways
     * @param PairData
     * @param PairsList
     * @param PairsTabs
     * @param WatchlistSearch
     * @return {DexWatchlist}
     */
    const controller = function (
        Base,
        waves,
        user,
        $scope,
        $state,
        $location,
        decorators,
        gateways,
        sepaGateways,
        PairData,
        PairsList,
        PairsTabs,
        WatchlistSearch
    ) {

        class DexWatchlist extends Base {

            constructor() {
                super();

                /**
                 * @type {Array}
                 */
                this.visiblePairsData = [];

                /**
                 * @type {PairsTabs}
                 */
                this.tabs = new PairsTabs();

                /**
                 * @type {PairsTab}
                 */
                this.tab = null;

                /**
                 * @type {Array}
                 * @private
                 */
                this.tabsData = [];

                /**
                 * @type {Array}
                 * @private
                 */
                this.dropDownData = [];

                /**
                 * @type {string}
                 */
                this.search = '';

                /**
                 * @type {*[]}
                 */
                this.headers = [
                    {
                        id: 'favourite',
                        templatePath: 'modules/dex/directives/dexWatchlist/FavouritesColumnHeader.html',
                        scopeData: {
                            toggleOnlyFavourite: () => {
                                this.toggleOnlyFavourite();
                            },
                            shouldShowOnlyFavourite: () => {
                                return this.shouldShowOnlyFavourite();
                            }
                        }
                    },
                    {
                        id: 'pair',
                        title: { literal: 'directives.watchlist.pair' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByPairAscending(),
                                () => this.tab.sortByPairDescending()
                            );
                        }
                    },
                    {
                        id: 'price',
                        title: { literal: 'directives.watchlist.price' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByPriceAscending(),
                                () => this.tab.sortByPriceDescending()
                            );
                        }
                    },
                    {
                        id: 'change',
                        title: { literal: 'directives.watchlist.chg' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByChangeAscending(),
                                () => this.tab.sortByChangeDescending()
                            );
                        }
                    },
                    {
                        id: 'volume',
                        title: { literal: 'directives.watchlist.volume' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByVolumeAscending(),
                                () => this.tab.sortByVolumeDescending()
                            );

                        }
                    },
                    {
                        id: 'info'
                    }
                ];

                /**
                 * @type {string[]}
                 * @private
                 */
                this._chosenPair = null;

                /**
                 * @type {Array<string>}
                 * @private
                 */
                this._assetsIds = [];

                /**
                 * @type {{amount: string, price: string}}
                 * @private
                 */
                this._assetIdPair = null;

                /**
                 * @type {boolean}
                 * @private
                 */
                this._shouldShowOnlyFavourite = false;

                /**
                 * @type {{}}
                 * @private
                 */
                this._favourite = {};
            }

            $postLink() {
                this.syncSettings({
                    _favourite: 'dex.watchlist.favourite',
                    _assetsIds: 'dex.watchlist.list',
                    _assetIdPair: 'dex.assetIdPair'
                });

                this._prepareTabs();
                this.tab = this.tabs.getChosenTab();
                this._chooseInitialPair();

                this.observe('search', this._applyFilteringAndPrepareSearchResults);
                this.observe('_chosenPair', this._switchLocationAndUpdateAssetIdPair);
            }

            /**
             * @param pair
             */
            choosePair(pair) {
                this.tab.choosePair(pair);
                this._updateVisiblePairsData();
                this._simplyChoosePair(pair);
            }

            /**
             * @param tabData
             */
            chooseTab(tabData) {
                this.tabs.switchTabTo(tabData.id).then(() => {
                    this._updateVisiblePairsData();
                });

                this.tab = this.tabs.getChosenTab();
                this._prepareSearchResults();
            }

            /**
             * @param tabData
             * @returns {boolean}
             */
            isActive(tabData) {
                return this.tab.isBasedOn(tabData);
            }

            /**
             * @param pair
             * @returns {boolean}
             */
            isChosen(pair) {
                return this.tab.isChosen(pair);
            }

            /**
             * @param pair
             * @returns {Boolean}
             */
            isFavourite(pair) {
                return this.tab.isFavourite(pair);
            }

            /**
             * @param {string} change
             * @returns {boolean}
             */
            isNegative(change) {
                return parseFloat(change) < 0;
            }

            /**
             * @param {string} change
             * @returns {boolean}
             */
            isPositive(change) {
                return parseFloat(change) > 0;
            }

            /**
             * @returns {boolean}
             */
            shouldShowOnlyFavourite() {
                return this._shouldShowOnlyFavourite;
            }

            /**
             * @param $event
             * @param pair
             */
            toggleFavourite($event, pair) {
                $event.stopPropagation();

                this.tab.toggleFavourite(pair)
                    .then(() => {
                        this._updateVisiblePairsData();
                    });

                this._saveFavouriteForTab(this.tab.id, this.tab.getFavourite());
            }

            _saveFavouriteForTab(tabId, favourite) {
                this._favourite[tabId] = favourite;
                this._favourite = Object.assign({}, this._favourite);
                user.setSetting('dex.watchlist.favourite', this._favourite);
            }

            toggleOnlyFavourite() {
                this._shouldShowOnlyFavourite = !this._shouldShowOnlyFavourite;
                this._updateVisiblePairsData();
            }

            /**
             * @private
             */
            _applyFilteringAndPrepareSearchResults() {
                // Applies filter.
                this._updateVisiblePairsData();

                this._prepareSearchResults();
            }

            /**
             * @param assetId
             * @param assetIds
             * @returns {Array}
             * @private
             */
            _buildPairsRelativeTo(assetId, assetIds) {
                const pairs = [];

                assetIds.forEach((id) => {
                    if (assetId === id) {
                        return;
                    }

                    pairs.push([assetId, id]);
                });

                return pairs;
            }

            /**
             * @private
             */
            _chooseInitialPair() {
                this._simplyChoosePair(this.tab.getChosenPair() || this.tab.getDefaultPair());
                this._switchLocationAndUpdateAssetIdPair();
            }

            /**
             * @param assetId
             * @param tabId
             * @returns {Array}
             * @private
             */
            _getFavouritePairsRelativeTo(assetId, tabId = assetId) {
                const savedFavourite = this._getSavedFavourite(tabId);

                if (savedFavourite) {
                    return savedFavourite;
                }

                const allGateways = Object.assign({}, { WAVES: '' }, gateways, sepaGateways);
                const pairsRelativeToAsset = this._buildPairsRelativeTo(assetId, Object.keys(allGateways));
                this._saveFavouriteForTab(tabId, pairsRelativeToAsset);

                return pairsRelativeToAsset;
            }

            /**
             * @returns {Array}
             * @private
             */
            _getOtherPairs() {
                const otherPairs = [];

                this._assetsIds.forEach((assetId, index) => {
                    this._assetsIds
                        .slice(index + 1)
                        .forEach((anotherAssetId) => {
                            otherPairs.push([assetId, anotherAssetId]);
                        });
                });

                return otherPairs;
            }

            /**
             * @param assetId
             * @returns {Array}
             * @private
             */
            _getOtherPairsRelativeTo(assetId) {
                return this._buildPairsRelativeTo(assetId, this._assetsIds);
            }

            _getSearchQuery() {
                return `${this.tab.getSearchPrefix()}${this.search}`;
            }

            /**
             * @returns {*}
             * @private
             */
            _prepareSearchResults() {
                if (!this.search) {
                    this.tab.clearSearchResults();
                    this._updateVisiblePairsData();
                    return;
                }

                WatchlistSearch.search(this._getSearchQuery())
                    .then((searchResults) => {
                        return this.tab.setSearchResults(searchResults.results)
                            .then(() => {
                                this._updateVisiblePairsData();
                            });
                    });
            }

            /**
             * @param assetName
             * @returns {{title: *, id: *, pairsOfIds: {favourite: *, other: *[][]}}}
             * @private
             */
            _prepareTabDataForAsset(assetName) {
                const id = WavesApp.defaultAssets[assetName];

                return {
                    title: assetName,
                    id,
                    searchPrefix: `${id}/`,
                    pairsOfIds: {
                        favourite: this._getFavouritePairsRelativeTo(id),
                        other: this._getOtherPairsRelativeTo(id)
                    }
                };
            }

            /**
             * @param assetsNames
             * @returns {*}
             * @private
             */
            _prepareTabDataForAssets(assetsNames) {
                return assetsNames.map((assetName) => this._prepareTabDataForAsset(assetName));
            }

            /**
             * @private
             */
            _prepareTabs() {
                const ALL = 'All';

                this.tabsData = [
                    {
                        title: ALL,
                        id: ALL,
                        searchPrefix: '',
                        pairsOfIds: {
                            favourite: this._getFavouritePairsRelativeTo(WavesApp.defaultAssets.WAVES, ALL),
                            other: this._getOtherPairs(),
                            chosen: DexWatchlist._getPairFromState()
                        }
                    },
                    ...this._prepareTabDataForAssets(TABS_ASSETS)
                ];

                const allTabs = TABS_ASSETS.concat(DROP_DOWN_ASSETS);
                const otherTabs = (
                    Object
                        .keys(WavesApp.defaultAssets)
                        .filter((defaultAsset) => !allTabs.includes(defaultAsset))
                );
                this.dropDownData = [...this._prepareTabDataForAssets(DROP_DOWN_ASSETS.concat(otherTabs))];

                this.tabs
                    .addPairs([...this.tabsData, ...this.dropDownData])
                    .then(() => {
                        this._updateVisiblePairsData();
                    });
            }

            /**
             * @param tabId
             * @returns {{}|*|null}
             * @private
             */
            _getSavedFavourite(tabId) {
                return (
                    this._favourite &&
                    this._favourite[tabId]
                ) || null;
            }

            /**
             * @param pair
             * @private
             */
            _simplyChoosePair(pair) {
                this._chosenPair = pair;
            }

            /**
             * @param shouldSortAscending
             * @param sortAscending
             * @param sortDescending
             * @returns {any[]}
             * @private
             */
            _sortColumn(shouldSortAscending, sortAscending, sortDescending) {
                if (shouldSortAscending) {
                    sortAscending();
                } else {
                    sortDescending();
                }

                return this.tab.getVisiblePairs(this._shouldShowOnlyFavourite);
            }

            /**
             * @private
             */
            _switchLocationAndUpdateAssetIdPair() {
                $location.search('assetId1', this._chosenPair.pairOfIds[0]);
                $location.search('assetId2', this._chosenPair.pairOfIds[1]);

                this._chosenPair.amountAndPriceRequest.then(() => {
                    this._assetIdPair = {
                        amount: this._chosenPair.amountAsset.id,
                        price: this._chosenPair.priceAsset.id
                    };
                });
            }

            /**
             * @private
             */
            _updateVisiblePairsData() {
                WatchlistSearch.filter(this.tab.getVisiblePairs(this._shouldShowOnlyFavourite), this._getSearchQuery())
                    .then((filterResults) => {
                        this.visiblePairsData = filterResults;
                        $scope.$digest();
                    });
            }

            /**
             * @returns {*}
             * @private
             */
            static _getPairFromState() {
                const { assetId1, assetId2 } = $state.params;

                if (assetId1 && assetId2) {
                    return [
                        $state.params.assetId1,
                        $state.params.assetId2
                    ];
                }

                return null;
            }

        }

        return new DexWatchlist();
    };

    controller.$inject = [
        'Base',
        'waves',
        'user',
        '$scope',
        '$state',
        '$location',
        'decorators',
        'gateways',
        'sepaGateways',
        'PairData',
        'PairsList',
        'PairsTabs',
        'WatchlistSearch'
    ];

    angular.module('app.dex')
        .component('wDexWatchlist', {
            templateUrl: 'modules/dex/directives/dexWatchlist/DexWatchlist.html',
            controller
        });
})();
<commit_msg>CLIENT-872: fix saving of favourites according to specifics of our sync service<commit_after>(function () {
    'use strict';

    const TABS_ASSETS = ['WAVES', 'BTC'];
    // Other gateways are added dynamically in the code below.
    const DROP_DOWN_ASSETS = ['ETH', 'BCH', 'LTC', 'USD', 'EUR'];

    /**
     * @param Base
     * @param {Waves} waves
     * @param user
     * @param {$rootScope.Scope} $scope
     * @param {$state} $state
     * @param {$location} $location
     * @param {app.utils.decorators} decorators
     * @param gateways
     * @param sepaGateways
     * @param PairData
     * @param PairsList
     * @param PairsTabs
     * @param WatchlistSearch
     * @return {DexWatchlist}
     */
    const controller = function (
        Base,
        waves,
        user,
        $scope,
        $state,
        $location,
        decorators,
        gateways,
        sepaGateways,
        PairData,
        PairsList,
        PairsTabs,
        WatchlistSearch
    ) {

        class DexWatchlist extends Base {

            constructor() {
                super();

                /**
                 * @type {Array}
                 */
                this.visiblePairsData = [];

                /**
                 * @type {PairsTabs}
                 */
                this.tabs = new PairsTabs();

                /**
                 * @type {PairsTab}
                 */
                this.tab = null;

                /**
                 * @type {Array}
                 * @private
                 */
                this.tabsData = [];

                /**
                 * @type {Array}
                 * @private
                 */
                this.dropDownData = [];

                /**
                 * @type {string}
                 */
                this.search = '';

                /**
                 * @type {*[]}
                 */
                this.headers = [
                    {
                        id: 'favourite',
                        templatePath: 'modules/dex/directives/dexWatchlist/FavouritesColumnHeader.html',
                        scopeData: {
                            toggleOnlyFavourite: () => {
                                this.toggleOnlyFavourite();
                            },
                            shouldShowOnlyFavourite: () => {
                                return this.shouldShowOnlyFavourite();
                            }
                        }
                    },
                    {
                        id: 'pair',
                        title: { literal: 'directives.watchlist.pair' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByPairAscending(),
                                () => this.tab.sortByPairDescending()
                            );
                        }
                    },
                    {
                        id: 'price',
                        title: { literal: 'directives.watchlist.price' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByPriceAscending(),
                                () => this.tab.sortByPriceDescending()
                            );
                        }
                    },
                    {
                        id: 'change',
                        title: { literal: 'directives.watchlist.chg' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByChangeAscending(),
                                () => this.tab.sortByChangeDescending()
                            );
                        }
                    },
                    {
                        id: 'volume',
                        title: { literal: 'directives.watchlist.volume' },
                        sort: (pairs, shouldSortAscending) => {
                            return this._sortColumn(
                                shouldSortAscending,
                                () => this.tab.sortByVolumeAscending(),
                                () => this.tab.sortByVolumeDescending()
                            );

                        }
                    },
                    {
                        id: 'info'
                    }
                ];

                /**
                 * @type {string[]}
                 * @private
                 */
                this._chosenPair = null;

                /**
                 * @type {Array<string>}
                 * @private
                 */
                this._assetsIds = [];

                /**
                 * @type {{amount: string, price: string}}
                 * @private
                 */
                this._assetIdPair = null;

                /**
                 * @type {boolean}
                 * @private
                 */
                this._shouldShowOnlyFavourite = false;

                /**
                 * @type {{}}
                 * @private
                 */
                this._favourite = {};
            }

            $postLink() {
                this.syncSettings({
                    _favourite: 'dex.watchlist.favourite',
                    _assetsIds: 'dex.watchlist.list',
                    _assetIdPair: 'dex.assetIdPair'
                });

                this._prepareTabs();
                this.tab = this.tabs.getChosenTab();
                this._chooseInitialPair();

                this.observe('search', this._applyFilteringAndPrepareSearchResults);
                this.observe('_chosenPair', this._switchLocationAndUpdateAssetIdPair);
            }

            /**
             * @param pair
             */
            choosePair(pair) {
                this.tab.choosePair(pair);
                this._updateVisiblePairsData();
                this._simplyChoosePair(pair);
            }

            /**
             * @param tabData
             */
            chooseTab(tabData) {
                this.tabs.switchTabTo(tabData.id).then(() => {
                    this._updateVisiblePairsData();
                });

                this.tab = this.tabs.getChosenTab();
                this._prepareSearchResults();
            }

            /**
             * @param tabData
             * @returns {boolean}
             */
            isActive(tabData) {
                return this.tab.isBasedOn(tabData);
            }

            /**
             * @param pair
             * @returns {boolean}
             */
            isChosen(pair) {
                return this.tab.isChosen(pair);
            }

            /**
             * @param pair
             * @returns {Boolean}
             */
            isFavourite(pair) {
                return this.tab.isFavourite(pair);
            }

            /**
             * @param {string} change
             * @returns {boolean}
             */
            isNegative(change) {
                return parseFloat(change) < 0;
            }

            /**
             * @param {string} change
             * @returns {boolean}
             */
            isPositive(change) {
                return parseFloat(change) > 0;
            }

            /**
             * @returns {boolean}
             */
            shouldShowOnlyFavourite() {
                return this._shouldShowOnlyFavourite;
            }

            /**
             * @param $event
             * @param pair
             */
            toggleFavourite($event, pair) {
                $event.stopPropagation();

                this.tab.toggleFavourite(pair)
                    .then(() => {
                        this._updateVisiblePairsData();
                    });

                this._saveFavouriteForTab(this.tab.id, this.tab.getFavourite());
            }

            _saveFavouriteForTab(tabId, favouritePairsOfIds) {
                // This order of operations is required for proper work of synchronization of settings.
                const favourite = tsUtils.cloneDeep(this._favourite);
                favourite[tabId] = favouritePairsOfIds;
                this._favourite = favourite;
            }

            toggleOnlyFavourite() {
                this._shouldShowOnlyFavourite = !this._shouldShowOnlyFavourite;
                this._updateVisiblePairsData();
            }

            /**
             * @private
             */
            _applyFilteringAndPrepareSearchResults() {
                // Applies filter.
                this._updateVisiblePairsData();

                this._prepareSearchResults();
            }

            /**
             * @param assetId
             * @param assetIds
             * @returns {Array}
             * @private
             */
            _buildPairsRelativeTo(assetId, assetIds) {
                const pairs = [];

                assetIds.forEach((id) => {
                    if (assetId === id) {
                        return;
                    }

                    pairs.push([assetId, id]);
                });

                return pairs;
            }

            /**
             * @private
             */
            _chooseInitialPair() {
                this._simplyChoosePair(this.tab.getChosenPair() || this.tab.getDefaultPair());
                this._switchLocationAndUpdateAssetIdPair();
            }

            /**
             * @param assetId
             * @param tabId
             * @returns {Array}
             * @private
             */
            _getFavouritePairsRelativeTo(assetId, tabId = assetId) {
                const savedFavourite = this._getSavedFavourite(tabId);

                if (savedFavourite) {
                    return savedFavourite;
                }

                const allGateways = Object.assign({}, { WAVES: '' }, gateways, sepaGateways);
                const pairsRelativeToAsset = this._buildPairsRelativeTo(assetId, Object.keys(allGateways));
                this._saveFavouriteForTab(tabId, pairsRelativeToAsset);

                return pairsRelativeToAsset;
            }

            /**
             * @returns {Array}
             * @private
             */
            _getOtherPairs() {
                const otherPairs = [];

                this._assetsIds.forEach((assetId, index) => {
                    this._assetsIds
                        .slice(index + 1)
                        .forEach((anotherAssetId) => {
                            otherPairs.push([assetId, anotherAssetId]);
                        });
                });

                return otherPairs;
            }

            /**
             * @param assetId
             * @returns {Array}
             * @private
             */
            _getOtherPairsRelativeTo(assetId) {
                return this._buildPairsRelativeTo(assetId, this._assetsIds);
            }

            _getSearchQuery() {
                return `${this.tab.getSearchPrefix()}${this.search}`;
            }

            /**
             * @returns {*}
             * @private
             */
            _prepareSearchResults() {
                if (!this.search) {
                    this.tab.clearSearchResults();
                    this._updateVisiblePairsData();
                    return;
                }

                WatchlistSearch.search(this._getSearchQuery())
                    .then((searchResults) => {
                        return this.tab.setSearchResults(searchResults.results)
                            .then(() => {
                                this._updateVisiblePairsData();
                            });
                    });
            }

            /**
             * @param assetName
             * @returns {{title: *, id: *, pairsOfIds: {favourite: *, other: *[][]}}}
             * @private
             */
            _prepareTabDataForAsset(assetName) {
                const id = WavesApp.defaultAssets[assetName];

                return {
                    title: assetName,
                    id,
                    searchPrefix: `${id}/`,
                    pairsOfIds: {
                        favourite: this._getFavouritePairsRelativeTo(id),
                        other: this._getOtherPairsRelativeTo(id)
                    }
                };
            }

            /**
             * @param assetsNames
             * @returns {*}
             * @private
             */
            _prepareTabDataForAssets(assetsNames) {
                return assetsNames.map((assetName) => this._prepareTabDataForAsset(assetName));
            }

            /**
             * @private
             */
            _prepareTabs() {
                const ALL = 'All';

                this.tabsData = [
                    {
                        title: ALL,
                        id: ALL,
                        searchPrefix: '',
                        pairsOfIds: {
                            favourite: this._getFavouritePairsRelativeTo(WavesApp.defaultAssets.WAVES, ALL),
                            other: this._getOtherPairs(),
                            chosen: DexWatchlist._getPairFromState()
                        }
                    },
                    ...this._prepareTabDataForAssets(TABS_ASSETS)
                ];

                const allTabs = TABS_ASSETS.concat(DROP_DOWN_ASSETS);
                const otherTabs = (
                    Object
                        .keys(WavesApp.defaultAssets)
                        .filter((defaultAsset) => !allTabs.includes(defaultAsset))
                );
                this.dropDownData = [...this._prepareTabDataForAssets(DROP_DOWN_ASSETS.concat(otherTabs))];

                this.tabs
                    .addPairs([...this.tabsData, ...this.dropDownData])
                    .then(() => {
                        this._updateVisiblePairsData();
                    });
            }

            /**
             * @param tabId
             * @returns {{}|*|null}
             * @private
             */
            _getSavedFavourite(tabId) {
                return (
                    this._favourite &&
                    this._favourite[tabId]
                ) || null;
            }

            /**
             * @param pair
             * @private
             */
            _simplyChoosePair(pair) {
                this._chosenPair = pair;
            }

            /**
             * @param shouldSortAscending
             * @param sortAscending
             * @param sortDescending
             * @returns {any[]}
             * @private
             */
            _sortColumn(shouldSortAscending, sortAscending, sortDescending) {
                if (shouldSortAscending) {
                    sortAscending();
                } else {
                    sortDescending();
                }

                return this.tab.getVisiblePairs(this._shouldShowOnlyFavourite);
            }

            /**
             * @private
             */
            _switchLocationAndUpdateAssetIdPair() {
                $location.search('assetId1', this._chosenPair.pairOfIds[0]);
                $location.search('assetId2', this._chosenPair.pairOfIds[1]);

                this._chosenPair.amountAndPriceRequest.then(() => {
                    this._assetIdPair = {
                        amount: this._chosenPair.amountAsset.id,
                        price: this._chosenPair.priceAsset.id
                    };
                });
            }

            /**
             * @private
             */
            _updateVisiblePairsData() {
                WatchlistSearch.filter(this.tab.getVisiblePairs(this._shouldShowOnlyFavourite), this._getSearchQuery())
                    .then((filterResults) => {
                        this.visiblePairsData = filterResults;
                        $scope.$digest();
                    });
            }

            /**
             * @returns {*}
             * @private
             */
            static _getPairFromState() {
                const { assetId1, assetId2 } = $state.params;

                if (assetId1 && assetId2) {
                    return [
                        $state.params.assetId1,
                        $state.params.assetId2
                    ];
                }

                return null;
            }

        }

        return new DexWatchlist();
    };

    controller.$inject = [
        'Base',
        'waves',
        'user',
        '$scope',
        '$state',
        '$location',
        'decorators',
        'gateways',
        'sepaGateways',
        'PairData',
        'PairsList',
        'PairsTabs',
        'WatchlistSearch'
    ];

    angular.module('app.dex')
        .component('wDexWatchlist', {
            templateUrl: 'modules/dex/directives/dexWatchlist/DexWatchlist.html',
            controller
        });
})();
<|endoftext|>


<commit_before>/**
 * Module for display of locations and activities filters
 *
 * @param {object} p_options
 * @author  Bret Davidson <bret_davidson@ncsu.edu>
 */
var ReportFilters = function (p_options) {

    var options = {
            url: '', // URL for AJAX request (initiative dictionary)
            triggerForm: '', // Form element that triggers AJAX request (CSS ID)
            filterForm: '', // Wrapper element that controls visibility of filters (CSS ID)
            locationsTemplate: '', // CSS ID of locations template
            activitiesTemplate: '', // CSS ID of activities template
            locationsSelect: '', // CSS ID of locations select list
            activitiesSelect: '' // CSS ID of activities select list
        };

    return {
        /**
         * Initializes module
         *
         * @this {ReportFilters}
         */
        init: function () {
            var dfd = $.Deferred();

            // Check passed options. Copied from http://www.engfers.com/code/javascript-module-pattern/
            if (p_options !== null && p_options !== undefined && p_options !== 'undefined') {
                _.each(options, function (element, index) {
                    if (p_options[index] !== null && p_options[index] !== undefined && p_options[index] !== 'undefined') {
                        options[index] = p_options[index];
                    }
                });
            }

            // Bind event listeners
            $.when(this.bindEvents())
                .then(function () {
                    dfd.resolve();
                }, function (e) {
                    dfd.reject(e);
                });

            return dfd.promise();
        },
        /**
         * Binds event listener for AJAX call for dictionary
         * and filter display
         */
        bindEvents: function () {
            var dfd = $.Deferred(),
                self = this;

            // Listen for change of initiative
            $(options.triggerForm).on('change', function (e) {
                if (this.value !== 'default') {
                    $(options.filterForm).hide();
                        $.when(self.getDictionary(this.value))
                        .then(function (data) {
                            self.buildInterfaceElements(data);
                            dfd.resolve();
                        }, function (e) {
                            dfd.reject(e);
                        });
                } else {
                    $(options.filterForm).fadeOut();
                }
            });

            return dfd.promise();
        },
        /**
         * Processes data and populates templates for filters
         *
         * @param {object} data
         * @this {ReportFilters}
         */
        buildInterfaceElements: function (data) {
            // Process locations and activities
            var locations = this.processLocations(data.locations, data.rootLocation),
                activities = this.processActivities(data.activities, data.activityGroups);

            // Set properties to access elsewhere
            this.locations = locations;
            this.activities = activities;

            // Populate templates
            this.buildTemplate(locations, options.locationsTemplate, options.locationsSelect);
            this.buildTemplate(activities, options.activitiesTemplate, options.activitiesSelect);

            // Show new filters
            $(options.filterForm).fadeIn();
        },
        /**
         * AJAX call to retrieve dictionary
         *
         * @param  {string} initiative
         * @return {object} Returns a jQuery promise object.
         */
        getDictionary: function (initiative) {
            // AJAX call is returned here to take advantage of jQuery promise object
            return $.ajax({
                data: {
                    id: initiative
                },
                dataType: 'json',
                url: options.url,
                beforeSend: function () {
                    $(options.triggerForm).attr('disabled', 'true');
                    $('#secondary-loading').show();
                },
                complete: function () {
                    $(options.triggerForm).removeAttr('disabled', 'true');
                    $('#secondary-loading').hide();
                },
                timeout: 60000 // 1 minute
            });
        },
        /**
         * Sort activities by rank, meant to be used
         * with native arr.sort() method
         *
         * @param  {object} a
         * @param  {object} b
         * @return {integer}
         */
        sortActivities: function (a, b) {
            return a.rank - b.rank;
        },
        /**
         * Calculate depth of location in adjacency list
         *
         * @param  {object}  item
         * @param  {array}   list
         * @param  {integer} root
         * @return {integer}
         */
        calculateDepth: function (item, list, root, depth) {
            var parent;

            depth = depth || 0;

            if (item.parent === root) {
                return depth;
            }

            depth += 1;

            parent = _.find(list, {'id': item.parent});

            return this.calculateDepth(parent, list, root, depth);
        },
        /**
         * Build a sorted list of locations
         *
         * @param  {arr} locations
         * @param  {arr} rootLocation
         * @return {arr}
         */
        processLocations: function (locations, rootLocation) {
            var locList,
                self = this;

            locList = d3.nest()
                .sortKeys(function (d) { return d.parent; })
                .sortKeys(function (d) { return d.rank; })
                .rollup(function (values) {
                    _.each(values, function (item, i, list) {
                        item.depth = self.calculateDepth(item, list, rootLocation);
                    });
                    return values;
                })
                .entries(locations);

            return locList;
        },
        /**
         * Build a sorted list of activities
         *
         * @param  {arr} activities
         * @param  {arr} activityGroups
         * @return {arr}
         */
        processActivities: function (activities, activityGroups) {
            var activityList = [];

            // Sort activities and activity groups
            activities.sort(this.sortActivities);
            activityGroups.sort(this.sortActivities);

            // For each activity group, build a list of activities
            _.each(activityGroups, function (activityGroup) {
                // Activity group metadata
                var listItem = {
                    'id'   : activityGroup.id,
                    'rank' : activityGroup.rank,
                    'title': activityGroup.title,
                    'type' : 'activityGroup',
                    'depth': 0
                };

                // Add activity group activityList array
                activityList.push(listItem);

                // Loop over activities and add the ones belonging to the current activityGroup
                _.each(activities, function (activity, index) {
                    if (activity.activityGroup === activityGroup.id) {
                        // Activity metadata
                        var listItem = {
                            'id'   : activity.id,
                            'rank' : activity.rank,
                            'title': activity.title,
                            'type' : 'activity',
                            'depth': 1,
                            'activityGroup': activityGroup.id
                        };

                        // Add activities to activityList array behind proper activityGroup
                        activityList.push(listItem);

                    }
                });
            });

            return activityList;
        },
        /**
         * Build and insert template
         *
         * @param  {arr} items
         * @param  {string} templateId
         * @param  {string} elementId
         */
        buildTemplate: function (items, templateId, elementId) {
            var html,
                json,
                template;

            // Insert list into object for template iteration
            json = {items: items};

            // Retrieve template from index.php (in script tag)
            html = $(templateId).html();

            // Compile template
            template = Handlebars.compile(html);

            // Template helper to convert depth to emdash
            Handlebars.registerHelper('indent', function (depth) {
                var indent = '';
                while (depth > 0) {
                    depth -= 1;
                    indent += '—';
                }
                return indent;
            });

            // Populate template with data and insert into DOM
            $(elementId).empty();
            $(elementId).append(template(json));
        }
    };
};<commit_msg>fix em dash display in handlebars template<commit_after>/**
 * Module for display of locations and activities filters
 *
 * @param {object} p_options
 * @author  Bret Davidson <bret_davidson@ncsu.edu>
 */
var ReportFilters = function (p_options) {

    var options = {
            url: '', // URL for AJAX request (initiative dictionary)
            triggerForm: '', // Form element that triggers AJAX request (CSS ID)
            filterForm: '', // Wrapper element that controls visibility of filters (CSS ID)
            locationsTemplate: '', // CSS ID of locations template
            activitiesTemplate: '', // CSS ID of activities template
            locationsSelect: '', // CSS ID of locations select list
            activitiesSelect: '' // CSS ID of activities select list
        };

    return {
        /**
         * Initializes module
         *
         * @this {ReportFilters}
         */
        init: function () {
            var dfd = $.Deferred();

            // Check passed options. Copied from http://www.engfers.com/code/javascript-module-pattern/
            if (p_options !== null && p_options !== undefined && p_options !== 'undefined') {
                _.each(options, function (element, index) {
                    if (p_options[index] !== null && p_options[index] !== undefined && p_options[index] !== 'undefined') {
                        options[index] = p_options[index];
                    }
                });
            }

            // Bind event listeners
            $.when(this.bindEvents())
                .then(function () {
                    dfd.resolve();
                }, function (e) {
                    dfd.reject(e);
                });

            return dfd.promise();
        },
        /**
         * Binds event listener for AJAX call for dictionary
         * and filter display
         */
        bindEvents: function () {
            var dfd = $.Deferred(),
                self = this;

            // Listen for change of initiative
            $(options.triggerForm).on('change', function (e) {
                if (this.value !== 'default') {
                    $(options.filterForm).hide();
                        $.when(self.getDictionary(this.value))
                        .then(function (data) {
                            self.buildInterfaceElements(data);
                            dfd.resolve();
                        }, function (e) {
                            dfd.reject(e);
                        });
                } else {
                    $(options.filterForm).fadeOut();
                }
            });

            return dfd.promise();
        },
        /**
         * Processes data and populates templates for filters
         *
         * @param {object} data
         * @this {ReportFilters}
         */
        buildInterfaceElements: function (data) {
            // Process locations and activities
            var locations = this.processLocations(data.locations, data.rootLocation),
                activities = this.processActivities(data.activities, data.activityGroups);

            // Set properties to access elsewhere
            this.locations = locations;
            this.activities = activities;

            // Populate templates
            this.buildTemplate(locations, options.locationsTemplate, options.locationsSelect);
            this.buildTemplate(activities, options.activitiesTemplate, options.activitiesSelect);

            // Show new filters
            $(options.filterForm).fadeIn();
        },
        /**
         * AJAX call to retrieve dictionary
         *
         * @param  {string} initiative
         * @return {object} Returns a jQuery promise object.
         */
        getDictionary: function (initiative) {
            // AJAX call is returned here to take advantage of jQuery promise object
            return $.ajax({
                data: {
                    id: initiative
                },
                dataType: 'json',
                url: options.url,
                beforeSend: function () {
                    $(options.triggerForm).attr('disabled', 'true');
                    $('#secondary-loading').show();
                },
                complete: function () {
                    $(options.triggerForm).removeAttr('disabled', 'true');
                    $('#secondary-loading').hide();
                },
                timeout: 60000 // 1 minute
            });
        },
        /**
         * Sort activities by rank, meant to be used
         * with native arr.sort() method
         *
         * @param  {object} a
         * @param  {object} b
         * @return {integer}
         */
        sortActivities: function (a, b) {
            return a.rank - b.rank;
        },
        /**
         * Calculate depth of location in adjacency list
         *
         * @param  {object}  item
         * @param  {array}   list
         * @param  {integer} root
         * @return {integer}
         */
        calculateDepth: function (item, list, root, depth) {
            var parent;

            depth = depth || 0;

            if (item.parent === root) {
                return depth;
            }

            depth += 1;

            parent = _.find(list, {'id': item.parent});

            return this.calculateDepth(parent, list, root, depth);
        },
        /**
         * Build a sorted list of locations
         *
         * @param  {arr} locations
         * @param  {arr} rootLocation
         * @return {arr}
         */
        processLocations: function (locations, rootLocation) {
            var locList,
                self = this;

            locList = d3.nest()
                .sortKeys(function (d) { return d.parent; })
                .sortKeys(function (d) { return d.rank; })
                .rollup(function (values) {
                    _.each(values, function (item, i, list) {
                        item.depth = self.calculateDepth(item, list, rootLocation);
                    });
                    return values;
                })
                .entries(locations);

            return locList;
        },
        /**
         * Build a sorted list of activities
         *
         * @param  {arr} activities
         * @param  {arr} activityGroups
         * @return {arr}
         */
        processActivities: function (activities, activityGroups) {
            var activityList = [];

            // Sort activities and activity groups
            activities.sort(this.sortActivities);
            activityGroups.sort(this.sortActivities);

            // For each activity group, build a list of activities
            _.each(activityGroups, function (activityGroup) {
                // Activity group metadata
                var listItem = {
                    'id'   : activityGroup.id,
                    'rank' : activityGroup.rank,
                    'title': activityGroup.title,
                    'type' : 'activityGroup',
                    'depth': 0
                };

                // Add activity group activityList array
                activityList.push(listItem);

                // Loop over activities and add the ones belonging to the current activityGroup
                _.each(activities, function (activity, index) {
                    if (activity.activityGroup === activityGroup.id) {
                        // Activity metadata
                        var listItem = {
                            'id'   : activity.id,
                            'rank' : activity.rank,
                            'title': activity.title,
                            'type' : 'activity',
                            'depth': 1,
                            'activityGroup': activityGroup.id
                        };

                        // Add activities to activityList array behind proper activityGroup
                        activityList.push(listItem);

                    }
                });
            });

            return activityList;
        },
        /**
         * Build and insert template
         *
         * @param  {arr} items
         * @param  {string} templateId
         * @param  {string} elementId
         */
        buildTemplate: function (items, templateId, elementId) {
            var html,
                json,
                template;

            // Insert list into object for template iteration
            json = {items: items};

            // Retrieve template from index.php (in script tag)
            html = $(templateId).html();

            // Compile template
            template = Handlebars.compile(html);

            // Template helper to convert depth to emdash
            Handlebars.registerHelper('indent', function (depth) {
                var indent = '';
                while (depth > 0) {
                    depth -= 1;
                    indent += '—';
                }
                return indent;
            });

            // Populate template with data and insert into DOM
            $(elementId).empty();
            $(elementId).append(template(json));
        }
    };
};<|endoftext|>


<commit_before>var bidfactory = require('../bidfactory.js');
var bidmanager = require('../bidmanager.js');
var adloader = require('../adloader.js');

var PulsePointAdapter = function PulsePointAdapter() {

    var getJsStaticUrl = 'http://tag.contextweb.com/getjs.static.js';
    var bidUrl = 'http://tag.contextweb.com/bid';

    function _callBids(params) {
        if(typeof window.pp === 'undefined') {
            adloader.loadScript(getJsStaticUrl, function() { bid(params); });
        } else {
            bid(params);
        }
    }

    function bid(params) {
        var bids = params.bids;
        for (var i = 0; i < bids.length; i++) {
            var bidRequest = bids[i];
            var callback = bidResponseCallback(bidRequest);
            var ppBidRequest = new window.pp.Ad({
                cf : bidRequest.params.cf,
                cp : bidRequest.params.cp,
                ct : bidRequest.params.ct,
                cn : 1,
                ca : window.pp.requestActions.BID,
                cu : bidUrl,
                adUnitId: bidRequest.placementCode,
                callback: callback
            });
            ppBidRequest.display();
        }
    }

    function bidResponseCallback(bid) {
        return function(bidResponse) {
                    bidResponseAvailable(bid, bidResponse);
                };
    }

    function bidResponseAvailable(bidRequest, bidResponse) {
        if(bidResponse) {
            var adSize = bidRequest.params.cf.split('X');
            var bid = bidfactory.createBid(1);
            bid.bidderCode = bidRequest.bidder;
            bid.cpm = bidResponse.bidCpm;
            bid.ad = bidResponse.html;
            bid.width = adSize[0];
            bid.height = adSize[1];
            bidmanager.addBidResponse(bidRequest.placementCode, bid);
        }
    }

    return {
        callBids: _callBids
    };

};

module.exports = PulsePointAdapter;
<commit_msg>Minor fix<commit_after>var bidfactory = require('../bidfactory.js');
var bidmanager = require('../bidmanager.js');
var adloader = require('../adloader.js');

var PulsePointAdapter = function PulsePointAdapter() {

    var getJsStaticUrl = 'http://tag.contextweb.com/getjs.static.js';
    var bidUrl = 'http://tag.contextweb.com/bid';

    function _callBids(params) {
        if(typeof window.pp === 'undefined') {
            adloader.loadScript(getJsStaticUrl, function() { bid(params); });
        } else {
            bid(params);
        }
    }

    function bid(params) {
        var bids = params.bids;
        for (var i = 0; i < bids.length; i++) {
            var bidRequest = bids[i];
            var callback = bidResponseCallback(bidRequest);
            var ppBidRequest = new window.pp.Ad({
                cf : bidRequest.params.cf,
                cp : bidRequest.params.cp,
                ct : bidRequest.params.ct,
                cn : 1,
                ca : window.pp.requestActions.BID,
                cu : bidUrl,
                adUnitId: bidRequest.placementCode,
                callback: callback
            });
            ppBidRequest.display();
        }
    }

    function bidResponseCallback(bid) {
        return function(bidResponse) {
                    bidResponseAvailable(bid, bidResponse);
                };
    }

    function bidResponseAvailable(bidRequest, bidResponse) {
        if(bidResponse) {
            var adSize = bidRequest.params.cf.toUpperCase().split('X');
            var bid = bidfactory.createBid(1);
            bid.bidderCode = bidRequest.bidder;
            bid.cpm = bidResponse.bidCpm;
            bid.ad = bidResponse.html;
            bid.width = adSize[0];
            bid.height = adSize[1];
            bidmanager.addBidResponse(bidRequest.placementCode, bid);
        }
    }

    return {
        callBids: _callBids
    };

};

module.exports = PulsePointAdapter;
<|endoftext|>


<commit_before>'use strict';

// Configuring the User feeds module
angular.module('user-feeds').run(['Menus',
	function(Menus) {
		// Add the User feeds dropdown item
		Menus.addMenuItem('topbar', {
			title: 'User feeds',
			state: 'user-feeds',
			type: 'dropdown'
		});

		// Add the dropdown list item
		Menus.addSubMenuItem('topbar', 'user-feeds', {
			title: 'List User feeds',
			state: 'user-feeds.list'
		});

		// Add the dropdown create item
		Menus.addSubMenuItem('topbar', 'user-feeds', {
			title: 'Create User feed',
			state: 'user-feeds.create'
		});
	}
]);<commit_msg>user-feeds: remove user feeds entry from menu bar<commit_after>'use strict';

// Configuring the User feeds module
angular.module('user-feeds').run(['Menus',
	function(Menus) {
	}
]);
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import angular from 'angular';

import DEMO_INFO from 'app/data/demo-info.json';
import templateUrl from 'views/templates/demo-info.tpl.pug';

angular
.module('ignite-console.demo', [
    'ignite-console.socket'
])
.config(['$stateProvider', ($stateProvider) => {
    $stateProvider
        .state('demo', {
            abstract: true,
            url: '/demo',
            template: '<ui-view></ui-view>'
        })
        .state('demo.resume', {
            url: '/resume',
            permission: 'demo',
            redirectTo: 'default-state',
            unsaved: true,
            tfMetaTags: {
                title: 'Demo resume'
            }
        })
        .state('demo.reset', {
            url: '/reset',
            permission: 'demo',
            redirectTo: (trans) => {
                const $http = trans.injector().get('$http');

                return $http.post('/api/v1/demo/reset')
                    .then(() => 'default-state')
                    .catch((err) => {
                        trans.injector().get('IgniteMessages').showError(err);

                        return 'default-state';
                    });
            },
            unsaved: true,
            tfMetaTags: {
                title: 'Demo reset'
            }
        });
}])
.provider('Demo', ['$stateProvider', '$httpProvider', 'igniteSocketFactoryProvider', function($state, $http, socketFactory) {
    if (/(\/demo.*)/ig.test(location.pathname))
        sessionStorage.setItem('IgniteDemoMode', 'true');

    const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true';

    if (enabled) {
        socketFactory.set({query: 'IgniteDemoMode=true'});

        $http.interceptors.push('demoInterceptor');
    }

    this.$get = ['$rootScope', ($root) => {
        $root.IgniteDemoMode = enabled;

        return {enabled};
    }];
}])
.factory('demoInterceptor', ['Demo', function(Demo) {
    const isApiRequest = (url) => /\/api\/v1/ig.test(url);

    return {
        request(cfg) {
            if (Demo.enabled && isApiRequest(cfg.url))
                cfg.headers.IgniteDemoMode = true;

            return cfg;
        }
    };
}])
.controller('demoController', ['$scope', '$state', '$window', 'IgniteConfirm', function($scope, $state, $window, Confirm) {
    const _openTab = (stateName) => $window.open($state.href(stateName), '_blank');

    $scope.startDemo = () => {
        if (!$scope.user.demoCreated)
            return _openTab('demo.reset');

        Confirm.confirm('Would you like to continue with previous demo session?', true, false)
            .then((resume) => {
                if (resume)
                    return _openTab('demo.resume');

                _openTab('demo.reset');
            });
    };

    $scope.closeDemo = () => {
        $window.close();
    };
}])
.provider('igniteDemoInfo', [function() {
    const items = DEMO_INFO;

    this.update = (data) => items[0] = data;

    this.$get = [() => {
        return items;
    }];
}])
.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'AgentManager', function($rootScope, $modal, $state, $q, igniteDemoInfo, agentMgr) {
    const scope = $rootScope.$new();

    let closePromise = null;

    function _fillPage() {
        const model = igniteDemoInfo;

        scope.title = model[0].title;
        scope.message = model[0].message.join(' ');
    }

    const dialog = $modal({
        templateUrl,
        scope,
        show: false,
        backdrop: 'static'
    });

    scope.close = () => {
        dialog.hide();

        closePromise && closePromise.resolve();
    };

    scope.downloadAgent = () => {
        const lnk = document.createElement('a');

        lnk.setAttribute('href', '/api/v1/agent/downloads/agent');
        lnk.setAttribute('target', '_self');
        lnk.setAttribute('download', null);
        lnk.style.display = 'none';

        document.body.appendChild(lnk);

        lnk.click();

        document.body.removeChild(lnk);
    };

    return {
        show: () => {
            closePromise = $q.defer();

            _fillPage();

            return dialog.$promise
                .then(dialog.show)
                .then(() => Promise.race([agentMgr.awaitCluster(), closePromise.promise]))
                .then(() => scope.hasAgents = true);
        }
    };
}]);
<commit_msg>IGNITE-8842 Web console: Fixed initial screen in demo mode.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import angular from 'angular';

import DEMO_INFO from 'app/data/demo-info.json';
import templateUrl from 'views/templates/demo-info.tpl.pug';

const DEMO_QUERY_STATE = {state: 'base.sql.notebook', params: {noteId: 'demo'}};

angular
.module('ignite-console.demo', [
    'ignite-console.socket'
])
.config(['$stateProvider', ($stateProvider) => {
    $stateProvider
        .state('demo', {
            abstract: true,
            url: '/demo',
            template: '<ui-view></ui-view>'
        })
        .state('demo.resume', {
            url: '/resume',
            permission: 'demo',
            redirectTo: DEMO_QUERY_STATE,
            unsaved: true,
            tfMetaTags: {
                title: 'Demo resume'
            }
        })
        .state('demo.reset', {
            url: '/reset',
            permission: 'demo',
            redirectTo: (trans) => {
                const $http = trans.injector().get('$http');

                return $http.post('/api/v1/demo/reset')
                    .then(() => DEMO_QUERY_STATE)
                    .catch((err) => {
                        trans.injector().get('IgniteMessages').showError(err);

                        return DEMO_QUERY_STATE;
                    });
            },
            unsaved: true,
            tfMetaTags: {
                title: 'Demo reset'
            }
        });
}])
.provider('Demo', ['$stateProvider', '$httpProvider', 'igniteSocketFactoryProvider', function($state, $http, socketFactory) {
    if (/(\/demo.*)/ig.test(location.pathname))
        sessionStorage.setItem('IgniteDemoMode', 'true');

    const enabled = sessionStorage.getItem('IgniteDemoMode') === 'true';

    if (enabled) {
        socketFactory.set({query: 'IgniteDemoMode=true'});

        $http.interceptors.push('demoInterceptor');
    }

    this.$get = ['$rootScope', ($root) => {
        $root.IgniteDemoMode = enabled;

        return {enabled};
    }];
}])
.factory('demoInterceptor', ['Demo', function(Demo) {
    const isApiRequest = (url) => /\/api\/v1/ig.test(url);

    return {
        request(cfg) {
            if (Demo.enabled && isApiRequest(cfg.url))
                cfg.headers.IgniteDemoMode = true;

            return cfg;
        }
    };
}])
.controller('demoController', ['$scope', '$state', '$window', 'IgniteConfirm', function($scope, $state, $window, Confirm) {
    const _openTab = (stateName) => $window.open($state.href(stateName), '_blank');

    $scope.startDemo = () => {
        if (!$scope.user.demoCreated)
            return _openTab('demo.reset');

        Confirm.confirm('Would you like to continue with previous demo session?', true, false)
            .then((resume) => {
                if (resume)
                    return _openTab('demo.resume');

                _openTab('demo.reset');
            });
    };

    $scope.closeDemo = () => {
        $window.close();
    };
}])
.provider('igniteDemoInfo', [function() {
    const items = DEMO_INFO;

    this.update = (data) => items[0] = data;

    this.$get = [() => {
        return items;
    }];
}])
.service('DemoInfo', ['$rootScope', '$modal', '$state', '$q', 'igniteDemoInfo', 'AgentManager', function($rootScope, $modal, $state, $q, igniteDemoInfo, agentMgr) {
    const scope = $rootScope.$new();

    let closePromise = null;

    function _fillPage() {
        const model = igniteDemoInfo;

        scope.title = model[0].title;
        scope.message = model[0].message.join(' ');
    }

    const dialog = $modal({
        templateUrl,
        scope,
        show: false,
        backdrop: 'static'
    });

    scope.close = () => {
        dialog.hide();

        closePromise && closePromise.resolve();
    };

    scope.downloadAgent = () => {
        const lnk = document.createElement('a');

        lnk.setAttribute('href', '/api/v1/agent/downloads/agent');
        lnk.setAttribute('target', '_self');
        lnk.setAttribute('download', null);
        lnk.style.display = 'none';

        document.body.appendChild(lnk);

        lnk.click();

        document.body.removeChild(lnk);
    };

    return {
        show: () => {
            closePromise = $q.defer();

            _fillPage();

            return dialog.$promise
                .then(dialog.show)
                .then(() => Promise.race([agentMgr.awaitCluster(), closePromise.promise]))
                .then(() => scope.hasAgents = true);
        }
    };
}]);
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * String plugin, loads a string using a syntax like:
 * var title = require( 'string!JOHN_TRAVOLTAGE/johnTravoltage.name' );
 *
 * The reasons we need our own string plugin:
 * So we can only include the (possibly) strings that are needed for a sim
 * So we can enumerate all of the used strings, for purposed of a translation utility
 * For uniformity with image and audio plugin
 *
 * The plugin code itself is excluded from the build by declaring it as a stubModule
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var text = require( 'text' );
  var _ = require( '../../sherpa/lodash-2.4.1.min' );
  var getProjectURL = require( '../../chipper/requirejs-plugins/getProjectURL' );

  var FALLBACK_LOCALE = 'en';

  var parse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function') ? JSON.parse : function( text ) { return eval( '(' + text + ')' ); };

  //Cache the loaded strings so they only have to be file.read once
  var cache = {};

  //When running in the browser, check to see if we have already loaded the specified file
  //Also parses it so that only happens once per file (instead of once per string key)
  function getWithCache( url, callback, errback, headers ) {

    //Check for cache hit
    if ( cache[ url ] ) {
      callback( cache[ url ] );
    }

    //Cache miss: load the file parse, enter into cache and return it
    else {
      text.get( url, function( loadedText ) {
        cache[ url ] = parse( loadedText );
        callback( cache[ url ] );
      }, errback, headers );
    }
  }

  return {
    load: function( name, parentRequire, onload, config ) {

      //TODO why are we looking for a '?' in a string key name? when does that occur?
      // Pull out the key, which is between the last slash and the first question mark of the 'name' parameter.
      var questionMarkIndex = name.lastIndexOf( '?' );
      var key = questionMarkIndex < 0 ? name.substring( name.lastIndexOf( '/' ) + 1 ) : name.substring( name.lastIndexOf( '/' ) + 1, questionMarkIndex );

      // Create the paths to the string files - primary and fallback.
      var project = name.substring( 0, name.indexOf( '/' ) );

      //TODO remove this and fix the projects to use the proper convention
      //Some projects use a nickname for their module name, we can support that here
      project = project.toLowerCase() === 'bam' ? 'build-a-molecule' :
                project;

      //Apply the cache buster args (but only during requirejs mode)
      var suffix = config.isBuild ? '' : '?' + config.urlArgs;

      var getPath = function( locale ) {return getProjectURL( name, parentRequire ) + 'strings/' + project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale + '.json' + suffix;};
      var fallbackStringPath = getPath( FALLBACK_LOCALE );

      // strings may be specified via the 'strings' query parameter, value is expected to be encoded to avoid URI-reserved characters
      var queryParameterStrings = JSON.parse( decodeURIComponent( window.phetcommon.getQueryParameter( 'strings' ) || '{}' ) );

      var locale;
      //Browser version first
      if ( !config.isBuild ) {

        //Read the locale from a query parameter, if it is there, or use english
        locale = window.phetcommon.getQueryParameter( 'locale' ) || config.phetLocale || 'en';
        var stringPath = getPath( locale );

        // In the browser, a string specified via the 'strings' query parameter overrides anything,
        // to match the behavior of the chipper version (for dynamically substituting new strings like in the translation utility)
        var queryParameterStringValue = queryParameterStrings[ name ];
        if ( queryParameterStringValue ) {
          onload( queryParameterStringValue )
        }
        else {

          // Load & parse just once per file, getting the fallback strings first.
          getWithCache( fallbackStringPath, function( parsedFallbackStrings ) {
              var fallback = parsedFallbackStrings[ key ] || key;

              // Now get the primary strings.
              getWithCache( stringPath, function( parsed ) {

                  // Combine the primary and fallback strings into one object hash.
                  var parsedStrings = _.extend( parsedFallbackStrings, parsed );
                  if ( parsedStrings[ key ] !== undefined ) {
                    onload( parsedStrings[ key ] );
                  }
                  else {
                    console.log( 'string not found for key: ' + key );
                    onload( fallback );
                  }
                },
                //Error callback in the text! plugin.  Couldn't load the strings for the specified language, so use a fallback
                function() {

                  if ( !parsedFallbackStrings[ key ] ) {
                    //It would be really strange for there to be no fallback for a certain string, that means it exists in the translation but not the original English
                    console.log( 'no fallback for key:' + key );
                  }
                  //Running in the browser (dynamic requirejs mode) and couldn't find the string file.  Use the fallbacks.
                  console.log( "No string file provided for " + stringPath );
                  onload( fallback );
                },
                { accept: 'application/json' }
              )
            },
            onload.error,
            { accept: 'application/json' }
          )
        }
      }

      //For compiler time
      else {

        //Lookup all of the available translation files for the localesToBuild and the fallback string files
        var localesToLoad = global.phet.localesToBuild.slice();
        if ( localesToLoad.indexOf( FALLBACK_LOCALE ) < 0 ) {
          localesToLoad.push( FALLBACK_LOCALE );
        }

        var count = 0;
        var resourceHandled = function() {
          count++;
          if ( count >= localesToLoad.length ) {
            onload( null );
          }
        };
        for ( var i = 0; i < localesToLoad.length; i++ ) {
          (function( locale ) {

            var path = getPath( locale );

            //If we already loaded those strings and registered with global.phet.strings, no need to do so again
            if ( cache[ path ] ) {
              global.phet.strings[ locale ][ name ] = cache[ path ][ key ];
              resourceHandled();
            }

            //If the file doesn't exist, move on to the next one
            else if ( !global.fs.existsSync( path ) ) {
              console.log( "File doesn't exist: ", path );
              resourceHandled();
            }

            //Load from the actual file
            else {
              text.get( path, function( stringFile ) {
                  var parsed = parse( stringFile );

                  //Store all loaded strings for access in the gruntfile.
                  //Fallbacks are computed in the Gruntfile.js
                  global.phet.strings[ locale ][ name ] = parsed[ key ];
                  cache[ path ] = parsed;
                  resourceHandled();
                },
                onload.error,
                { accept: 'application/json' }
              )
            }
          })( localesToLoad[ i ] );
        }
      }
    },

    //Write code that will look up the string in the compiled sim.
    write: function( pluginName, moduleName, write ) {
      write( 'define("' + pluginName + '!' + moduleName + '",function(){return window.phetStrings.get(\"' + moduleName + '\");});\n' );
    }
  };
} );
<commit_msg>#83 remove code that maps 'BAM' to 'build-a-molecule'<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * String plugin, loads a string using a syntax like:
 * var title = require( 'string!JOHN_TRAVOLTAGE/johnTravoltage.name' );
 *
 * The reasons we need our own string plugin:
 * So we can only include the (possibly) strings that are needed for a sim
 * So we can enumerate all of the used strings, for purposed of a translation utility
 * For uniformity with image and audio plugin
 *
 * The plugin code itself is excluded from the build by declaring it as a stubModule
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var text = require( 'text' );
  var _ = require( '../../sherpa/lodash-2.4.1.min' );
  var getProjectURL = require( '../../chipper/requirejs-plugins/getProjectURL' );

  var FALLBACK_LOCALE = 'en';

  var parse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function') ? JSON.parse : function( text ) { return eval( '(' + text + ')' ); };

  //Cache the loaded strings so they only have to be file.read once
  var cache = {};

  //When running in the browser, check to see if we have already loaded the specified file
  //Also parses it so that only happens once per file (instead of once per string key)
  function getWithCache( url, callback, errback, headers ) {

    //Check for cache hit
    if ( cache[ url ] ) {
      callback( cache[ url ] );
    }

    //Cache miss: load the file parse, enter into cache and return it
    else {
      text.get( url, function( loadedText ) {
        cache[ url ] = parse( loadedText );
        callback( cache[ url ] );
      }, errback, headers );
    }
  }

  return {
    load: function( name, parentRequire, onload, config ) {

      //TODO why are we looking for a '?' in a string key name? when does that occur?
      // Pull out the key, which is between the last slash and the first question mark of the 'name' parameter.
      var questionMarkIndex = name.lastIndexOf( '?' );
      var key = questionMarkIndex < 0 ? name.substring( name.lastIndexOf( '/' ) + 1 ) : name.substring( name.lastIndexOf( '/' ) + 1, questionMarkIndex );

      // Create the paths to the string files - primary and fallback.
      var project = name.substring( 0, name.indexOf( '/' ) );

      //Apply the cache buster args (but only during requirejs mode)
      var suffix = config.isBuild ? '' : '?' + config.urlArgs;

      var getPath = function( locale ) {return getProjectURL( name, parentRequire ) + 'strings/' + project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale + '.json' + suffix;};
      var fallbackStringPath = getPath( FALLBACK_LOCALE );

      // strings may be specified via the 'strings' query parameter, value is expected to be encoded to avoid URI-reserved characters
      var queryParameterStrings = JSON.parse( decodeURIComponent( window.phetcommon.getQueryParameter( 'strings' ) || '{}' ) );

      var locale;
      //Browser version first
      if ( !config.isBuild ) {

        //Read the locale from a query parameter, if it is there, or use english
        locale = window.phetcommon.getQueryParameter( 'locale' ) || config.phetLocale || 'en';
        var stringPath = getPath( locale );

        // In the browser, a string specified via the 'strings' query parameter overrides anything,
        // to match the behavior of the chipper version (for dynamically substituting new strings like in the translation utility)
        var queryParameterStringValue = queryParameterStrings[ name ];
        if ( queryParameterStringValue ) {
          onload( queryParameterStringValue )
        }
        else {

          // Load & parse just once per file, getting the fallback strings first.
          getWithCache( fallbackStringPath, function( parsedFallbackStrings ) {
              var fallback = parsedFallbackStrings[ key ] || key;

              // Now get the primary strings.
              getWithCache( stringPath, function( parsed ) {

                  // Combine the primary and fallback strings into one object hash.
                  var parsedStrings = _.extend( parsedFallbackStrings, parsed );
                  if ( parsedStrings[ key ] !== undefined ) {
                    onload( parsedStrings[ key ] );
                  }
                  else {
                    console.log( 'string not found for key: ' + key );
                    onload( fallback );
                  }
                },
                //Error callback in the text! plugin.  Couldn't load the strings for the specified language, so use a fallback
                function() {

                  if ( !parsedFallbackStrings[ key ] ) {
                    //It would be really strange for there to be no fallback for a certain string, that means it exists in the translation but not the original English
                    console.log( 'no fallback for key:' + key );
                  }
                  //Running in the browser (dynamic requirejs mode) and couldn't find the string file.  Use the fallbacks.
                  console.log( "No string file provided for " + stringPath );
                  onload( fallback );
                },
                { accept: 'application/json' }
              )
            },
            onload.error,
            { accept: 'application/json' }
          )
        }
      }

      //For compiler time
      else {

        //Lookup all of the available translation files for the localesToBuild and the fallback string files
        var localesToLoad = global.phet.localesToBuild.slice();
        if ( localesToLoad.indexOf( FALLBACK_LOCALE ) < 0 ) {
          localesToLoad.push( FALLBACK_LOCALE );
        }

        var count = 0;
        var resourceHandled = function() {
          count++;
          if ( count >= localesToLoad.length ) {
            onload( null );
          }
        };
        for ( var i = 0; i < localesToLoad.length; i++ ) {
          (function( locale ) {

            var path = getPath( locale );

            //If we already loaded those strings and registered with global.phet.strings, no need to do so again
            if ( cache[ path ] ) {
              global.phet.strings[ locale ][ name ] = cache[ path ][ key ];
              resourceHandled();
            }

            //If the file doesn't exist, move on to the next one
            else if ( !global.fs.existsSync( path ) ) {
              console.log( "File doesn't exist: ", path );
              resourceHandled();
            }

            //Load from the actual file
            else {
              text.get( path, function( stringFile ) {
                  var parsed = parse( stringFile );

                  //Store all loaded strings for access in the gruntfile.
                  //Fallbacks are computed in the Gruntfile.js
                  global.phet.strings[ locale ][ name ] = parsed[ key ];
                  cache[ path ] = parsed;
                  resourceHandled();
                },
                onload.error,
                { accept: 'application/json' }
              )
            }
          })( localesToLoad[ i ] );
        }
      }
    },

    //Write code that will look up the string in the compiled sim.
    write: function( pluginName, moduleName, write ) {
      write( 'define("' + pluginName + '!' + moduleName + '",function(){return window.phetStrings.get(\"' + moduleName + '\");});\n' );
    }
  };
} );
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * String plugin, loads a string using a syntax like:
 * var title = require( 'string!JOHN_TRAVOLTAGE/johnTravoltage.name' );
 *
 * The reasons we need our own string plugin:
 * So we can only include the (possibly) strings that are needed for a sim
 * So we can enumerate all of the used strings, for purposed of a translation utility
 * For uniformity with image and audio plugin
 *
 * The plugin code itself is excluded from the build by declaring it as a stubModule
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var text = require( 'text' );
  var _ = require( '../../sherpa/lodash-2.0.0.min' );
  var getProjectURL = require( '../../chipper/requirejs-plugins/getProjectURL' );

  var FALLBACK_LOCALE = 'en';

  var parse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function') ? JSON.parse : function( text ) { return eval( '(' + text + ')' ); };

  //Cache the loaded strings so they only have to be file.read once
  var cache = {};

  //When running in the browser, check to see if we have already loaded the specified file
  //Also parses it so that only happens once per file (instead of once per string key)
  function getWithCache( url, callback, errback, headers ) {

    //Check for cache hit
    if ( cache[url] ) {
      callback( cache[url] );
    }

    //Cache miss: load the file parse, enter into cache and return it
    else {
      text.get( url, function( loadedText ) {
        cache[url] = parse( loadedText );
        callback( cache[url] );
      }, errback, headers );
    }
  }

  return {
    load: function( name, parentRequire, onload, config ) {

      // Pull out the key, which is between the last slash and the first question mark of the 'name' parameter.
      var questionMarkIndex = name.lastIndexOf( '?' );
      var key = questionMarkIndex < 0 ? name.substring( name.lastIndexOf( '/' ) + 1 ) : name.substring( name.lastIndexOf( '/' ) + 1, questionMarkIndex );

      // Create the paths to the string files - primary and fallback.
      var project = name.substring( 0, name.indexOf( '/' ) );
      project = project.toLowerCase() === 'bam' ? 'build-a-molecule' :
                project.toLowerCase() === 'woas' ? 'wave-on-a-string' :
                project;
      var getPath = function( locale ) {return getProjectURL( name, parentRequire ) + 'strings/' + project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale + '.json';};
      var fallbackStringPath = getPath( FALLBACK_LOCALE );

      var locale;
      //Browser version first
      if ( !config.isBuild ) {

        //Read the locale from a query parameter, if it is there, or use english
        locale = window.phetcommon.getQueryParameter( 'locale' ) || config.phetLocale || 'en';
        var stringPath = getPath( locale );

        //In the browser, a query parameter overrides anything, to match the behavior of the chipper version (for dynamically substituting new strings like in the translation utility)
        var queryParameterValue = window.phetcommon.getQueryParameter( key );
        if ( queryParameterValue ) {
          onload( queryParameterValue )
        }
        else {

          //Load & parse just once per file
          getWithCache( fallbackStringPath, function( parsedFallbackStrings ) {
              var fallback = parsedFallbackStrings[key] || key;

              // Now get the primary strings.
              getWithCache( stringPath, function( parsed ) {

                  // Combine the primary and fallback strings into one object hash.
                  var parsedStrings = _.extend( parsedFallbackStrings, parsed );
                  if ( parsedStrings[key] ) {
                    onload( parsedStrings[key] );
                  }
                  else {
                    console.log( 'string not found for key: ' + key );
                    onload( fallback );
                  }
                },
                //Error callback in the text! plugin.  Couldn't load the strings for the specified language, so use a fallback
                function() {

                  if ( !parsedFallbackStrings[key] ) {
                    //It would be really strange for there to be no fallback for a certain string, that means it exists in the translation but not the original English
                    console.log( 'no fallback for key:' + key );
                  }
                  //Running in the browser (dynamic requirejs mode) and couldn't find the string file.  Use the fallbacks.
                  console.log( "No string file provided for " + stringPath );
                  onload( fallback );
                },
                { accept: 'application/json' }
              )
            },
            onload.error,
            { accept: 'application/json' }
          )
        }
      }

      //For compiler time
      else {

        //Lookup all of the available translation files for the localesToBuild and the fallback string files
        var localesToLoad = global.phet.localesToBuild.slice();
        if ( localesToLoad.indexOf( FALLBACK_LOCALE ) < 0 ) {
          localesToLoad.push( FALLBACK_LOCALE );
        }

        var count = 0;
        var resourceHandled = function() {
          count++;
          if ( count >= localesToLoad.length ) {
            onload( null );
          }
        };
        for ( var i = 0; i < localesToLoad.length; i++ ) {
          (function( locale ) {

            var path = getPath( locale );

            //If we already loaded those strings and registered with global.phet.strings, no need to do so again
            if ( cache[path] ) {
              resourceHandled();
            }

            //If the file doesn't exist, move on to the next one
            else if ( !global.fs.existsSync( path ) ) {
              console.log( "File doesn't exist: ", path );
              resourceHandled();
            }

            //Load from the actual file
            else {
              text.get( path, function( stringFile ) {
                  var parsed = parse( stringFile );
                  global.phet.strings[locale][name] = parsed[key];
                  cache[path] = parsed;//It doesn't really matter what the right hand side is as long as it is truthy
                  resourceHandled();
                },
                onload.error,
                { accept: 'application/json' }
              )
            }
          })( localesToLoad[i] );
        }
      }
    },

    //Write code that will look up the string in the compiled sim.
    write: function( pluginName, moduleName, write ) {
      write( 'define("' + pluginName + '!' + moduleName + '",function(){return window.phetStrings.get(\"' + moduleName + '\");});\n' );
    }
  };
} );<commit_msg>fixed string key assignment during cache lookup<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * String plugin, loads a string using a syntax like:
 * var title = require( 'string!JOHN_TRAVOLTAGE/johnTravoltage.name' );
 *
 * The reasons we need our own string plugin:
 * So we can only include the (possibly) strings that are needed for a sim
 * So we can enumerate all of the used strings, for purposed of a translation utility
 * For uniformity with image and audio plugin
 *
 * The plugin code itself is excluded from the build by declaring it as a stubModule
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var text = require( 'text' );
  var _ = require( '../../sherpa/lodash-2.0.0.min' );
  var getProjectURL = require( '../../chipper/requirejs-plugins/getProjectURL' );

  var FALLBACK_LOCALE = 'en';

  var parse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function') ? JSON.parse : function( text ) { return eval( '(' + text + ')' ); };

  //Cache the loaded strings so they only have to be file.read once
  var cache = {};

  //When running in the browser, check to see if we have already loaded the specified file
  //Also parses it so that only happens once per file (instead of once per string key)
  function getWithCache( url, callback, errback, headers ) {

    //Check for cache hit
    if ( cache[url] ) {
      callback( cache[url] );
    }

    //Cache miss: load the file parse, enter into cache and return it
    else {
      text.get( url, function( loadedText ) {
        cache[url] = parse( loadedText );
        callback( cache[url] );
      }, errback, headers );
    }
  }

  return {
    load: function( name, parentRequire, onload, config ) {

      // Pull out the key, which is between the last slash and the first question mark of the 'name' parameter.
      var questionMarkIndex = name.lastIndexOf( '?' );
      var key = questionMarkIndex < 0 ? name.substring( name.lastIndexOf( '/' ) + 1 ) : name.substring( name.lastIndexOf( '/' ) + 1, questionMarkIndex );

      // Create the paths to the string files - primary and fallback.
      var project = name.substring( 0, name.indexOf( '/' ) );
      project = project.toLowerCase() === 'bam' ? 'build-a-molecule' :
                project.toLowerCase() === 'woas' ? 'wave-on-a-string' :
                project;
      var getPath = function( locale ) {return getProjectURL( name, parentRequire ) + 'strings/' + project.toLowerCase().split( '_' ).join( '-' ) + '-strings_' + locale + '.json';};
      var fallbackStringPath = getPath( FALLBACK_LOCALE );

      var locale;
      //Browser version first
      if ( !config.isBuild ) {

        //Read the locale from a query parameter, if it is there, or use english
        locale = window.phetcommon.getQueryParameter( 'locale' ) || config.phetLocale || 'en';
        var stringPath = getPath( locale );

        //In the browser, a query parameter overrides anything, to match the behavior of the chipper version (for dynamically substituting new strings like in the translation utility)
        var queryParameterValue = window.phetcommon.getQueryParameter( key );
        if ( queryParameterValue ) {
          onload( queryParameterValue )
        }
        else {

          //Load & parse just once per file
          getWithCache( fallbackStringPath, function( parsedFallbackStrings ) {
              var fallback = parsedFallbackStrings[key] || key;

              // Now get the primary strings.
              getWithCache( stringPath, function( parsed ) {

                  // Combine the primary and fallback strings into one object hash.
                  var parsedStrings = _.extend( parsedFallbackStrings, parsed );
                  if ( parsedStrings[key] ) {
                    onload( parsedStrings[key] );
                  }
                  else {
                    console.log( 'string not found for key: ' + key );
                    onload( fallback );
                  }
                },
                //Error callback in the text! plugin.  Couldn't load the strings for the specified language, so use a fallback
                function() {

                  if ( !parsedFallbackStrings[key] ) {
                    //It would be really strange for there to be no fallback for a certain string, that means it exists in the translation but not the original English
                    console.log( 'no fallback for key:' + key );
                  }
                  //Running in the browser (dynamic requirejs mode) and couldn't find the string file.  Use the fallbacks.
                  console.log( "No string file provided for " + stringPath );
                  onload( fallback );
                },
                { accept: 'application/json' }
              )
            },
            onload.error,
            { accept: 'application/json' }
          )
        }
      }

      //For compiler time
      else {

        //Lookup all of the available translation files for the localesToBuild and the fallback string files
        var localesToLoad = global.phet.localesToBuild.slice();
        if ( localesToLoad.indexOf( FALLBACK_LOCALE ) < 0 ) {
          localesToLoad.push( FALLBACK_LOCALE );
        }

        var count = 0;
        var resourceHandled = function() {
          count++;
          if ( count >= localesToLoad.length ) {
            onload( null );
          }
        };
        for ( var i = 0; i < localesToLoad.length; i++ ) {
          (function( locale ) {

            var path = getPath( locale );

            //If we already loaded those strings and registered with global.phet.strings, no need to do so again
            if ( cache[path] ) {
              global.phet.strings[locale][name] = cache[path][key];
              resourceHandled();
            }

            //If the file doesn't exist, move on to the next one
            else if ( !global.fs.existsSync( path ) ) {
              console.log( "File doesn't exist: ", path );
              resourceHandled();
            }

            //Load from the actual file
            else {
              text.get( path, function( stringFile ) {
                  var parsed = parse( stringFile );
                  global.phet.strings[locale][name] = parsed[key];
                  cache[path] = parsed;//It doesn't really matter what the right hand side is as long as it is truthy
                  resourceHandled();
                },
                onload.error,
                { accept: 'application/json' }
              )
            }
          })( localesToLoad[i] );
        }
      }
    },

    //Write code that will look up the string in the compiled sim.
    write: function( pluginName, moduleName, write ) {
      write( 'define("' + pluginName + '!' + moduleName + '",function(){return window.phetStrings.get(\"' + moduleName + '\");});\n' );
    }
  };
} );<|endoftext|>