<commit_before>import { get, getWithDefault, set } from '@ember/object';
import { isEmpty, isPresent } from '@ember/utils';
import { getOwner } from '@ember/application';

import { Mixin as M } from 'base-object';
import { isFunction, isPlainObject } from 'lodash-es';

const bearerErrorCodes = [
  'invalid_token',
  'unknown_token',
  'token_disabled',
  'unknown_client',
  'client_disabled',
  'unknown_account',
  'account_disabled'
];

const AuthenticatedMixin = M.create ({
  beforeModel (transition) {
    this._super (...arguments);

    this._checkSignedIn (transition);

    let authorized = isPresent (this.authorizations.scope) ?
      this.session.accessToken.supports (this.authorizations.scope) :
      true;

    let controller = this.controllerFor (this.routeName);
    set (controller, 'isAuthorized', authorized);

    if (!authorized) {
      transition.trigger ('unauthorized', transition);
    }
  },

  actions: {
    error (reason) {
      let errors = get (reason, 'errors');

      if (isEmpty (errors)) {
        return true;
      }

      for (let i = 0, len = errors.length; i < len; ++ i) {
        let error = errors[i];

        if (error.status === '403' && bearerErrorCodes.indexOf (error.code) !== -1) {
          // Redirect to sign in page, allowing the user to redirect back to the
          // original page. But, do not support the back button.
          let ENV = getOwner (this).resolveRegistration ('config:environment');
          let signInRoute = getWithDefault (ENV, 'gatekeeper.signInRoute', 'sign-in');

          // Display the error message.
          //this.send ('app:snackbar', { message: error.detail });

          // Force the user to sign out.
          this.session.forceSignOut ();
          this.replaceWith (signInRoute);

          return;
        }
      }

      return true;
    }
  },

  /**
   * Check that the current user is authenticated.
   *
   * @param transition
   * @private
   */
  _checkSignedIn (transition) {
    let isSignedIn = get (this, 'session.isSignedIn');

    if (!isSignedIn) {
      let ENV = getOwner (this).resolveRegistration ('config:environment');
      let signInRoute = getWithDefault (ENV, 'gatekeeper.signInRoute', 'sign-in');

      // Set the redirect to route so we can come back to this route when the
      // user has signed in.
      const queryParams = { redirect: transition.targetName };
      this.replaceWith (signInRoute, { queryParams });
    }
  },
});

function applyMixin (Clazz, options = {}) {
  AuthenticatedMixin.apply (Clazz.prototype);

  let { scope } = options;
  Object.assign (Clazz.prototype, { authorizations: { scope } });
}

export default function authenticated (param) {
  if (isFunction (param)) {
    return applyMixin (param);
  }
  else if (isPlainObject (param)) {
    return function (Route) {
      return applyMixin (Route, param);
    }
  }
}<commit_msg>fix: force signout of session<commit_after>import { get, getWithDefault, set } from '@ember/object';
import { isEmpty, isPresent } from '@ember/utils';
import { getOwner } from '@ember/application';

import { Mixin as M } from 'base-object';
import { isFunction, isPlainObject } from 'lodash-es';

const bearerErrorCodes = [
  'invalid_token',
  'unknown_token',
  'token_disabled',
  'unknown_client',
  'client_disabled',
  'unknown_account',
  'account_disabled'
];

const AuthenticatedMixin = M.create ({
  beforeModel (transition) {
    this._super (...arguments);

    this._checkSignedIn (transition);

    let authorized = isPresent (this.authorizations.scope) ?
      this.session.accessToken.supports (this.authorizations.scope) :
      true;

    let controller = this.controllerFor (this.routeName);
    set (controller, 'isAuthorized', authorized);

    if (!authorized) {
      transition.trigger ('unauthorized', transition);
    }
  },

  actions: {
    error (reason) {
      let errors = get (reason, 'errors');

      if (isEmpty (errors)) {
        return true;
      }

      for (let i = 0, len = errors.length; i < len; ++ i) {
        let error = errors[i];

        if (error.status === '403' && bearerErrorCodes.indexOf (error.code) !== -1) {
          // Redirect to sign in page, allowing the user to redirect back to the
          // original page. But, do not support the back button.
          let ENV = getOwner (this).resolveRegistration ('config:environment');
          let signInRoute = getWithDefault (ENV, 'gatekeeper.signInRoute', 'sign-in');

          // Display the error message.
          //this.send ('app:snackbar', { message: error.detail });

          // Force the user to sign out.
          this.session.signOut (true).then (() => {
            this.replaceWith (signInRoute);
          });

          return;
        }
      }

      return true;
    }
  },

  /**
   * Check that the current user is authenticated.
   *
   * @param transition
   * @private
   */
  _checkSignedIn (transition) {
    let isSignedIn = get (this, 'session.isSignedIn');

    if (!isSignedIn) {
      let ENV = getOwner (this).resolveRegistration ('config:environment');
      let signInRoute = getWithDefault (ENV, 'gatekeeper.signInRoute', 'sign-in');

      // Set the redirect to route so we can come back to this route when the
      // user has signed in.
      const queryParams = { redirect: transition.targetName };
      this.replaceWith (signInRoute, { queryParams });
    }
  },
});

function applyMixin (Clazz, options = {}) {
  AuthenticatedMixin.apply (Clazz.prototype);

  let { scope } = options;
  Object.assign (Clazz.prototype, { authorizations: { scope } });
}

export default function authenticated (param) {
  if (isFunction (param)) {
    return applyMixin (param);
  }
  else if (isPlainObject (param)) {
    return function (Route) {
      return applyMixin (Route, param);
    }
  }
}<|endoftext|>


<commit_before>/* globals Task */
const {Cu} = require("chrome");
const {PlacesProvider} = require("addon/PlacesProvider");
const Feed = require("addon/lib/Feed");
const {TOP_SITES_LENGTH} = require("common/constants");
const am = require("common/action-manager");
const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
const getScreenshots = require("addon/lib/getScreenshots");
const {isRootDomain} = require("addon/lib/utils");

Cu.import("resource://gre/modules/Task.jsm");

module.exports = class TopSitesFeed extends Feed {
  // Used by this.refresh
  getData() {
    return Task.spawn(function*() {
      const experiments = this.store.getState().Experiments.values;

      let links;
      // Get links from places
      if (experiments.originalNewTabSites || experiments.screenshots) {
        links = yield PlacesProvider.links.asyncGetTopNewTabSites();
      } else {
        links = yield PlacesProvider.links.getTopFrecentSites();
      }

      // Get metadata from PreviewProvider
      links = yield this.options.getCachedMetadata(links, "TOP_FRECENT_SITES_RESPONSE");

      // Get screenshots if the favicons are too small
      if (experiments.screenshots) {
        try {
          links = yield getScreenshots(links, site => {
            if (site.favicon_height >= 96 && site.favicon_width >= 96 && isRootDomain(site.url)) {
              // If we are at the "root domain path" and the icon is big enough,
              // we don't show a screenshot.
              return false;
            }
            return true;
          });
          links = links.map(link => {
            if (link.screenshot) {
              link.metadata_source = `${link.metadata_source}+Screenshot`;
            }
            return link;
          });
        } catch (e) {
          Cu.reportError(e);
        }
      }
      // Create the action
      return am.actions.Response("TOP_FRECENT_SITES_RESPONSE", links);
    }.bind(this));
  }
  onAction(state, action) {
    switch (action.type) {
      case am.type("APP_INIT"):
        // When the app first starts up, refresh the data.
        this.refresh("app was initializing");
        break;
      case am.type("RECEIVE_PLACES_CHANGES"):
        // When a user visits a site, if we don't have enough top sites yet, refresh the data.
        if (state.TopSites.rows.length < TOP_SITES_LENGTH) {
          this.refresh("there were not enough sites");
        }
        // When a user visits a site, if the last time we refreshed the data is greater than 15 minutes, refresh the data.
        else if (Date.now() - this.state.lastUpdated >= UPDATE_TIME) {
          this.refresh("the sites were too old");
        }
        break;
      case am.type("MANY_LINKS_CHANGED"):
        // manyLinksChanged is an event fired by Places when all history is cleared,
        // or when frecency of links change due to something like a sync
        this.refresh("frecency of many links changed");
        break;
    }
  }
};

module.exports.UPDATE_TIME = UPDATE_TIME;
<commit_msg>fix (experiment): #2165 use default top sites feed for screenshots experiment<commit_after>/* globals Task */
const {Cu} = require("chrome");
const {PlacesProvider} = require("addon/PlacesProvider");
const Feed = require("addon/lib/Feed");
const {TOP_SITES_LENGTH} = require("common/constants");
const am = require("common/action-manager");
const UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
const getScreenshots = require("addon/lib/getScreenshots");
const {isRootDomain} = require("addon/lib/utils");

Cu.import("resource://gre/modules/Task.jsm");

module.exports = class TopSitesFeed extends Feed {
  // Used by this.refresh
  getData() {
    return Task.spawn(function*() {
      const experiments = this.store.getState().Experiments.values;

      let links;
      // Get links from places
      if (experiments.originalNewTabSites) {
        links = yield PlacesProvider.links.asyncGetTopNewTabSites();
      } else {
        links = yield PlacesProvider.links.getTopFrecentSites();
      }

      // Get metadata from PreviewProvider
      links = yield this.options.getCachedMetadata(links, "TOP_FRECENT_SITES_RESPONSE");

      // Get screenshots if the favicons are too small
      if (experiments.screenshots) {
        try {
          links = yield getScreenshots(links, site => {
            if (site.favicon_height >= 96 && site.favicon_width >= 96 && isRootDomain(site.url)) {
              // If we are at the "root domain path" and the icon is big enough,
              // we don't show a screenshot.
              return false;
            }
            return true;
          });
          links = links.map(link => {
            if (link.screenshot) {
              link.metadata_source = `${link.metadata_source}+Screenshot`;
            }
            return link;
          });
        } catch (e) {
          Cu.reportError(e);
        }
      }
      // Create the action
      return am.actions.Response("TOP_FRECENT_SITES_RESPONSE", links);
    }.bind(this));
  }
  onAction(state, action) {
    switch (action.type) {
      case am.type("APP_INIT"):
        // When the app first starts up, refresh the data.
        this.refresh("app was initializing");
        break;
      case am.type("RECEIVE_PLACES_CHANGES"):
        // When a user visits a site, if we don't have enough top sites yet, refresh the data.
        if (state.TopSites.rows.length < TOP_SITES_LENGTH) {
          this.refresh("there were not enough sites");
        }
        // When a user visits a site, if the last time we refreshed the data is greater than 15 minutes, refresh the data.
        else if (Date.now() - this.state.lastUpdated >= UPDATE_TIME) {
          this.refresh("the sites were too old");
        }
        break;
      case am.type("MANY_LINKS_CHANGED"):
        // manyLinksChanged is an event fired by Places when all history is cleared,
        // or when frecency of links change due to something like a sync
        this.refresh("frecency of many links changed");
        break;
    }
  }
};

module.exports.UPDATE_TIME = UPDATE_TIME;
<|endoftext|>


<commit_before>/**
 * @file CascaderListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component, Fragment} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Theme from '../Theme';
import List from '../List';

import SelectMode from '../_statics/SelectMode';
import HorizontalDirection from '../_statics/HorizontalDirection';

import Calculation from '../_vendors/Calculation';
import Util from '../_vendors/Util';

class CascaderListItem extends Component {

    static SelectMode = SelectMode;
    static ExpandDirection = HorizontalDirection;
    static Theme = Theme;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    findDataIndex = () => {

        const {activatedPath, data, valueField, displayField} = this.props;

        for (let dataItem of data) {

            const index = activatedPath.findIndex(item => item
                && Util.getValueByValueField(item.node, valueField, displayField)
                === Util.getValueByValueField(dataItem, valueField, displayField));

            if (index > -1) {
                return index;
            }

        }

        return -1;

    };

    getActivatedIndex = () => {

        const {activatedPath, data} = this.props;

        if (!activatedPath || activatedPath.length < 1 || !data) {
            return -1;
        }

        const index = this.findDataIndex();
        return index < 0 ? -1 : activatedPath[index].index;

    };

    isExpanded = (node, index) => {
        return index === this.getActivatedIndex() && node.children && node.children.length > 0;
    };

    getCurrentPathNode = (index = this.getActivatedIndex()) => {

        const {data} = this.props;

        return index > -1 ?
            {index: index, node: data[index]}
            :
            null;

    };

    getPath = (index = this.getActivatedIndex()) => {

        const {path} = this.props,
            currentPathNode = this.getCurrentPathNode(index);

        return path.length > 0 ?
            [...path, currentPathNode]
            :
            [currentPathNode];

    };

    isListItemIndeterminate = node => {
        return Calculation.isNodeIndeterminate(node, this.props.value, this.props);
    };

    listItemRenderer = (node, index) => {

        const {expandDirection, valueField, displayField, descriptionField, expandedIconCls, renderer} = this.props;

        let text, desc;
        if (!renderer) {
            text = Util.getTextByDisplayField(node, displayField, valueField);
            desc = node[descriptionField] || null;
        }

        return (
            <Fragment>

                {
                    expandDirection === HorizontalDirection.LEFT && this.isExpanded(node, index) ?

                        :
                        null
                }

                {
                    renderer ?
                        renderer(node, index)
                        :
                        (
                            desc ?

                                        {text}

                                        {desc}

                                :
                                text
                        )
                }

                {
                    expandDirection === HorizontalDirection.RIGHT && this.isExpanded(node, index) ?

                        :
                        null
                }

            </Fragment>
        );

    };

    listItemClickHanlder = (node, index) => {

        const {data, disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly || data.disabled || data.isLoading || data.readOnly) {
            return;
        }

        const {onNodeClick} = this.props;
        onNodeClick && onNodeClick(node, index, this.getPath(index));

    };

    listItemSelectHanlder = (node, index) => {
        const {onNodeSelect} = this.props;
        onNodeSelect && onNodeSelect(node, this.getPath(index));
    };

    listItemDeselectHanlder = (node, index) => {
        const {onNodeDeselect} = this.props;
        onNodeDeselect && onNodeDeselect(node, this.getPath(index));
    };

    render() {

        const {

                depth, theme, listWidth, selectTheme, selectMode, expandDirection, data, value,
                disabled, isLoading, readOnly, idField, valueField, displayField, descriptionField,

                radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls

            } = this.props,
            activatedIndex = this.getActivatedIndex(),
            hasChild = activatedIndex > -1 && data[activatedIndex] && data[activatedIndex].children
                && data[activatedIndex].children.length > 0,

            wrapperClassName = classNames('cascader-list-item', {
                expanded: hasChild
            }),

            listClassName = classNames('cascader-popup-list', {
                first: depth === 0,
                'expand-left': expandDirection === HorizontalDirection.LEFT
            }),
            listStyle = {
                width: listWidth
            };

        return (

                <List className={listClassName}
                      style={listStyle}
                      theme={theme}
                      selectTheme={selectTheme}
                      selectMode={selectMode}
                      data={data}
                      value={value}
                      disabled={disabled}
                      isLoading={isLoading}
                      readOnly={readOnly}
                      idField={idField}
                      valueField={valueField}
                      displayField={displayField}
                      descriptionField={descriptionField}
                      radioUncheckedIconCls={radioUncheckedIconCls}
                      radioCheckedIconCls={radioCheckedIconCls}
                      checkboxUncheckedIconCls={checkboxUncheckedIconCls}
                      checkboxCheckedIconCls={checkboxCheckedIconCls}
                      checkboxIndeterminateIconCls={checkboxIndeterminateIconCls}
                      autoSelect={selectMode !== SelectMode.MULTI_SELECT}
                      stopSelectClickEventPropagation={true}
                      indeterminateCallback={this.isListItemIndeterminate}
                      renderer={this.listItemRenderer}
                      onItemClick={this.listItemClickHanlder}
                      onItemSelect={this.listItemSelectHanlder}
                      onItemDeselect={this.listItemDeselectHanlder}/>

                {
                    activatedIndex > -1 && data[activatedIndex] && data[activatedIndex].children
                    && data[activatedIndex].children.length > 0 ?
                        <CascaderListItem {...this.props}
                                          data={data[activatedIndex].children}
                                          depth={depth + 1}
                                          path={this.getPath(activatedIndex)}/>
                        :
                        null
                }

        );

    }
}

CascaderListItem.propTypes = {

    index: PropTypes.number,
    depth: PropTypes.number,
    activatedPath: PropTypes.array,
    path: PropTypes.array,

    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    listWidth: PropTypes.number,

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),
    expandDirection: PropTypes.oneOf(Util.enumerateValue(HorizontalDirection)),

    data: PropTypes.array,
    value: PropTypes.any,

    idField: PropTypes.string,
    valueField: PropTypes.string,
    displayField: PropTypes.string,
    descriptionField: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    readOnly: PropTypes.bool,
    isNodeToggling: PropTypes.bool,
    isSelectRecursive: PropTypes.bool,

    renderer: PropTypes.func,

    expandedIconCls: PropTypes.string,
    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    onNodeClick: PropTypes.func,
    onNodeSelect: PropTypes.func,
    onNodeDeselect: PropTypes.func

};

CascaderListItem.defaultProps = {

    index: 0,
    depth: 0,
    activatedPath: [],
    path: [],

    theme: Theme.DEFAULT,
    listWidth: 200,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,
    expandDirection: HorizontalDirection.LEFT,

    idField: 'id',
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',

    disabled: false,
    isLoading: false,
    readOnly: false,
    isNodeToggling: false,
    isSelectRecursive: false,

    checkboxUncheckedIconCls: 'far fa-square',
    checkboxCheckedIconCls: 'fas fa-check-square',
    checkboxIndeterminateIconCls: 'fas fa-minus-square'

};

export default CascaderListItem;<commit_msg>update CascaderListItem<commit_after>/**
 * @file CascaderListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component, Fragment} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Theme from '../Theme';
import List from '../List';

import SelectMode from '../_statics/SelectMode';
import HorizontalDirection from '../_statics/HorizontalDirection';

import Calculation from '../_vendors/Calculation';
import Util from '../_vendors/Util';

class CascaderListItem extends Component {

    static SelectMode = SelectMode;
    static ExpandDirection = HorizontalDirection;
    static Theme = Theme;

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    findDataIndex = () => {

        const {activatedPath, data, valueField, displayField} = this.props;

        for (let dataItem of data) {

            const index = activatedPath.findIndex(item => item
                && Util.getValueByValueField(item.node, valueField, displayField)
                === Util.getValueByValueField(dataItem, valueField, displayField));

            if (index > -1) {
                return index;
            }

        }

        return -1;

    };

    getActivatedIndex = () => {

        const {activatedPath, data} = this.props;

        if (!activatedPath || activatedPath.length < 1 || !data) {
            return -1;
        }

        const index = this.findDataIndex();
        return index < 0 ? -1 : activatedPath[index].index;

    };

    isExpanded = (node, index) => {
        return index === this.getActivatedIndex() && node.children && node.children.length > 0;
    };

    getCurrentPathNode = (index = this.getActivatedIndex()) => {

        const {data} = this.props;

        return index > -1 ?
            {index: index, node: data[index]}
            :
            null;

    };

    getPath = (index = this.getActivatedIndex()) => {

        const {path} = this.props,
            currentPathNode = this.getCurrentPathNode(index);

        return path.length > 0 ?
            [...path, currentPathNode]
            :
            [currentPathNode];

    };

    isListItemIndeterminate = node => {

        if (!this.props.isSelectRecursive) {
            return false;
        }

        return Calculation.isNodeIndeterminate(node, this.props.value, this.props);

    };

    listItemRenderer = (node, index) => {

        const {expandDirection, valueField, displayField, descriptionField, expandedIconCls, renderer} = this.props;

        let text, desc;
        if (!renderer) {
            text = Util.getTextByDisplayField(node, displayField, valueField);
            desc = node[descriptionField] || null;
        }

        return (
            <Fragment>

                {
                    expandDirection === HorizontalDirection.LEFT && this.isExpanded(node, index) ?

                        :
                        null
                }

                {
                    renderer ?
                        renderer(node, index)
                        :
                        (
                            desc ?

                                        {text}

                                        {desc}

                                :
                                text
                        )
                }

                {
                    expandDirection === HorizontalDirection.RIGHT && this.isExpanded(node, index) ?

                        :
                        null
                }

            </Fragment>
        );

    };

    listItemClickHanlder = (node, index) => {

        const {data, disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly || data.disabled || data.isLoading || data.readOnly) {
            return;
        }

        const {onNodeClick} = this.props;
        onNodeClick && onNodeClick(node, index, this.getPath(index));

    };

    listItemSelectHanlder = (node, index) => {
        const {onNodeSelect} = this.props;
        onNodeSelect && onNodeSelect(node, this.getPath(index));
    };

    listItemDeselectHanlder = (node, index) => {
        const {onNodeDeselect} = this.props;
        onNodeDeselect && onNodeDeselect(node, this.getPath(index));
    };

    render() {

        const {

                depth, theme, listWidth, selectTheme, selectMode, expandDirection, data, value,
                disabled, isLoading, readOnly, idField, valueField, displayField, descriptionField,

                radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls

            } = this.props,
            activatedIndex = this.getActivatedIndex(),
            hasChild = activatedIndex > -1 && data[activatedIndex] && data[activatedIndex].children
                && data[activatedIndex].children.length > 0,

            wrapperClassName = classNames('cascader-list-item', {
                expanded: hasChild
            }),

            listClassName = classNames('cascader-popup-list', {
                first: depth === 0,
                'expand-left': expandDirection === HorizontalDirection.LEFT
            }),
            listStyle = {
                width: listWidth
            };

        return (

                <List className={listClassName}
                      style={listStyle}
                      theme={theme}
                      selectTheme={selectTheme}
                      selectMode={selectMode}
                      data={data}
                      value={value}
                      disabled={disabled}
                      isLoading={isLoading}
                      readOnly={readOnly}
                      idField={idField}
                      valueField={valueField}
                      displayField={displayField}
                      descriptionField={descriptionField}
                      radioUncheckedIconCls={radioUncheckedIconCls}
                      radioCheckedIconCls={radioCheckedIconCls}
                      checkboxUncheckedIconCls={checkboxUncheckedIconCls}
                      checkboxCheckedIconCls={checkboxCheckedIconCls}
                      checkboxIndeterminateIconCls={checkboxIndeterminateIconCls}
                      autoSelect={selectMode !== SelectMode.MULTI_SELECT}
                      stopSelectClickEventPropagation={true}
                      indeterminateCallback={this.isListItemIndeterminate}
                      renderer={this.listItemRenderer}
                      onItemClick={this.listItemClickHanlder}
                      onItemSelect={this.listItemSelectHanlder}
                      onItemDeselect={this.listItemDeselectHanlder}/>

                {
                    activatedIndex > -1 && data[activatedIndex] && data[activatedIndex].children
                    && data[activatedIndex].children.length > 0 ?
                        <CascaderListItem {...this.props}
                                          data={data[activatedIndex].children}
                                          depth={depth + 1}
                                          path={this.getPath(activatedIndex)}/>
                        :
                        null
                }

        );

    }
}

CascaderListItem.propTypes = {

    index: PropTypes.number,
    depth: PropTypes.number,
    activatedPath: PropTypes.array,
    path: PropTypes.array,

    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    listWidth: PropTypes.number,

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),
    expandDirection: PropTypes.oneOf(Util.enumerateValue(HorizontalDirection)),

    data: PropTypes.array,
    value: PropTypes.any,

    idField: PropTypes.string,
    valueField: PropTypes.string,
    displayField: PropTypes.string,
    descriptionField: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    readOnly: PropTypes.bool,
    isNodeToggling: PropTypes.bool,
    isSelectRecursive: PropTypes.bool,

    renderer: PropTypes.func,

    expandedIconCls: PropTypes.string,
    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    onNodeClick: PropTypes.func,
    onNodeSelect: PropTypes.func,
    onNodeDeselect: PropTypes.func

};

CascaderListItem.defaultProps = {

    index: 0,
    depth: 0,
    activatedPath: [],
    path: [],

    theme: Theme.DEFAULT,
    listWidth: 200,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,
    expandDirection: HorizontalDirection.LEFT,

    idField: 'id',
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',

    disabled: false,
    isLoading: false,
    readOnly: false,
    isNodeToggling: false,
    isSelectRecursive: false,

    checkboxUncheckedIconCls: 'far fa-square',
    checkboxCheckedIconCls: 'fas fa-check-square',
    checkboxIndeterminateIconCls: 'fas fa-minus-square'

};

export default CascaderListItem;<|endoftext|>


<commit_before>import { message, storage, openURL, __META__ } from '../browser-api'
import _ from 'lodash'

beforeEach(() => {
  browser.flush()
  delete window.pageId
  delete window.faviconURL
  delete window.pageTitle
  delete window.pageURL
})

describe('Browser API Wapper', () => {
  describe('Storage', () => {
    ['sync', 'local'].forEach(area => {
      it(`storage.${area}.clear`, () => {
        storage[area].clear()
        expect(browser.storage[area].clear.calledOnce).toBeTruthy()
      })
      it(`storage.${area}.remove`, () => {
        const key = `key-${area}`
        storage[area].remove(key)
        expect(browser.storage[area].remove.calledWith(key)).toBeTruthy()
      })
      it(`storage.${area}.get`, () => {
        const key = `key-${area}`
        storage[area].get(key)
        expect(browser.storage[area].get.calledWith(key)).toBeTruthy()
      })
      it(`storage.${area}.set`, () => {
        const key = { key: area }
        storage[area].set(key)
        expect(browser.storage[area].set.calledWith(key)).toBeTruthy()
      })
      it(`storage.${area}.addListener`, () => {
        const cb = () => {}
        storage[area].addListener(cb)
        expect(browser.storage.onChanged.addListener.calledOnce).toBeTruthy()
        expect(typeof __META__.storageListeners.get(cb).get(area)).toBe('function')
      })
      it(`storage.${area}.removeListener`, () => {
        const cb = jest.fn()
        storage[area].addListener(cb)
        storage[area].removeListener(cb)
        expect(browser.storage.onChanged.removeListener.calledOnce).toBeTruthy()
        expect(__META__.storageListeners.get(cb)).toBeUndefined()
        expect(cb).toHaveBeenCalledTimes(0)
      })
    })

    it.skip(`storage.clear`, () => {
      storage.clear()
      expect(browser.storage.sync.clear.calledOnce).toBeTruthy()
      expect(browser.storage.local.clear.calledOnce).toBeTruthy()
    })
    it(`storage.addListener`, () => {
      const cb = jest.fn()
      storage.addListener(cb)
      expect(browser.storage.onChanged.addListener.calledOnce).toBeTruthy()
      expect(typeof __META__.storageListeners.get(cb).get('all')).toBe('function')
      expect(cb).toHaveBeenCalledTimes(0)
    })
    it(`storage.removeListener`, () => {
      const cb = jest.fn()
      storage.addListener(cb)
      storage.removeListener(cb)
      expect(browser.storage.onChanged.removeListener.calledOnce).toBeTruthy()
      expect(__META__.storageListeners.get(cb)).toBeUndefined()
      expect(cb).toHaveBeenCalledTimes(0)
    })
  })

  describe('Message', () => {
    it('message.send', () => {
      const tabId = 1
      const msg = {}

      message.send(msg)
      expect(browser.runtime.sendMessage.calledWith(msg)).toBeTruthy()
      expect(browser.tabs.sendMessage.notCalled).toBeTruthy()

      browser.runtime.sendMessage.flush()
      browser.tabs.sendMessage.flush()

      message.send(tabId, msg)
      expect(browser.tabs.sendMessage.calledWith(tabId, msg)).toBeTruthy()
      expect(browser.runtime.sendMessage.notCalled).toBeTruthy()
    })
    it('message.addListener', () => {
      const cb1 = jest.fn()
      const cb2 = jest.fn()
      let cb1Call = 0
      let cb2Call = 0
      message.addListener(cb1)
      message.addListener('MSG_1', cb2)
      expect(browser.runtime.onMessage.addListener.calledTwice).toBeTruthy()
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)
      expect(__META__.messageListeners.get(cb1)).toBeTruthy()
      expect(__META__.messageListeners.get(cb2)).toBeTruthy()

      browser.runtime.onMessage.dispatch({ type: 'MSG_2' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(++cb2Call)
    })
    it('message.removeListener', () => {
      const cb1 = jest.fn()
      const cb2 = jest.fn()
      let cb1Call = 0
      let cb2Call = 0
      message.addListener('MSG_1', cb1)
      message.addListener('MSG_2', cb2)
      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      message.removeListener('MSG_x', cb1)
      message.removeListener(cb2)
      expect(browser.runtime.onMessage.removeListener.calledTwice).toBeTruthy()
      expect(__META__.messageListeners.get(cb1)).toBeTruthy()
      expect(__META__.messageListeners.get(cb2)).toBeUndefined()

      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      browser.runtime.onMessage.dispatch({ type: 'MSG_2' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      message.removeListener('MSG_1', cb1)
      expect(__META__.messageListeners.get(cb1)).toBeUndefined()
      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
    })

    it('message.self.initClient', () => {
      browser.runtime.sendMessage
        .withArgs({ type: '__PAGE_INFO__' })
        .returns(Promise.resolve({
          pageId: 'pageId',
          faviconURL: 'faviconURL',
          pageTitle: 'pageTitle',
          pageURL: 'pageURL',
        }))
      return message.self.initClient()
        .then(() => {
          expect(browser.runtime.sendMessage.calledWith({ type: '__PAGE_INFO__' })).toBeTruthy()
          expect(window.pageId).toBe('pageId')
          expect(window.faviconURL).toBe('faviconURL')
          expect(window.pageTitle).toBe('pageTitle')
          expect(window.pageURL).toBe('pageURL')
        })
    })
    describe('message.self.initServer', () => {
      const tab = {
        id: 1,
        favIconUrl: 'https://example.com/favIconUrl',
        url: 'https://example.com/url',
        title: 'title',
      }

      it('From tab', () => {
        message.self.initServer()
        expect(browser.runtime.onMessage.addListener.calledOnce).toBeTruthy()

        const sendResponse = jest.fn()
        browser.runtime.onMessage.dispatch({ type: '__PAGE_INFO__' }, { tab }, sendResponse)
        expect(sendResponse).toBeCalledWith(({
          pageId: tab.id,
          faviconURL: tab.favIconUrl,
          pageTitle: tab.title,
          pageURL: tab.url,
        }))
      })

      it('From browser action page', () => {
        message.self.initServer()
        expect(browser.runtime.onMessage.addListener.calledOnce).toBeTruthy()

        const sendResponse = jest.fn()
        browser.runtime.onMessage.dispatch({ type: '__PAGE_INFO__' }, {}, sendResponse)
        expect(sendResponse).toBeCalledWith(
          expect.objectContaining({
            pageId: 'popup',
          })
        )
      })

      it('Self page message transmission', () => {
        message.self.initServer()
        expect(browser.runtime.onMessage.addListener.calledOnce).toBeTruthy()

        browser.runtime.onMessage.dispatch({ type: '_&_MSG_1_&_', __pageId__: 1 }, {})
        expect(browser.runtime.sendMessage.calledWith({ type: 'MSG_1', __pageId__: 1 })).toBeTruthy()

        browser.runtime.onMessage.dispatch({ type: '_&_MSG_1_&_', __pageId__: 1 }, { tab })
        expect(browser.tabs.sendMessage.calledWith(tab.id, { type: 'MSG_1', __pageId__: 1 })).toBeTruthy()
      })
    })
    it('message.self.send', () => {
      window.pageId = 1
      message.self.send({
        type: 'MSG_1',
        prop: 'value',
      })
      expect(browser.runtime.sendMessage.calledWith({
        type: '_&_MSG_1_&_',
        __pageId__: window.pageId,
        prop: 'value',
      })).toBeTruthy()
    })
    it('message.self.addListener', () => {
      window.pageId = 1
      const cb1 = jest.fn()
      const cb2 = jest.fn()
      let cb1Call = 0
      let cb2Call = 0
      message.self.addListener(cb1)
      message.addListener(cb2)
      expect(browser.runtime.onMessage.addListener.calledTwice).toBeTruthy()
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)
      expect(__META__.messageSelfListeners.get(cb1)).toBeTruthy()
      expect(__META__.messageListeners.get(cb2)).toBeTruthy()

      browser.runtime.onMessage.dispatch({ type: 'MSG_1', __pageId__: window.pageId })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(++cb2Call)

      browser.runtime.onMessage.dispatch({ type: 'MSG_1', __pageId__: window.pageId + 2 })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)
    })
    it('message.self.removeListener', () => {
      window.pageId = 1
      const cb1 = jest.fn()
      let cb1Call = 0
      message.self.addListener(cb1)
      browser.runtime.onMessage.dispatch({ __pageId__: window.pageId })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)

      message.self.removeListener(cb1)
      expect(browser.runtime.onMessage.removeListener.calledOnce).toBeTruthy()
      expect(__META__.messageListeners.get(cb1)).toBeUndefined()

      browser.runtime.onMessage.dispatch({ __pageId__: window.pageId })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
    })
  })
  describe('openURL', () => {
    const url = 'https://example.com'

    it('Existing tab', () => {
      browser.tabs.query.returns(Promise.resolve(
        [{
          index: 1
        }]
      ))
      return openURL(url)
        .then(() => {
          expect(browser.tabs.query.calledWith({ url })).toBeTruthy()
          expect(browser.tabs.highlight.calledWith({ tabs: 1 })).toBeTruthy()
          expect(browser.tabs.create.notCalled).toBeTruthy()
        })
    })
    it('New tab', () => {
      browser.tabs.query.returns(Promise.resolve([]))
      return openURL(url)
        .then(() => {
          expect(browser.tabs.query.calledWith({ url })).toBeTruthy()
          expect(browser.tabs.highlight.notCalled).toBeTruthy()
          expect(browser.tabs.create.calledWith({ url })).toBeTruthy()
        })
    })
  })
})
<commit_msg>Fixed wait for sinon-chrome to publish the correct package<commit_after>import { message, storage, openURL, __META__ } from '../browser-api'

beforeEach(() => {
  browser.flush()
  delete window.pageId
  delete window.faviconURL
  delete window.pageTitle
  delete window.pageURL
})

describe('Browser API Wapper', () => {
  describe('Storage', () => {
    ['sync', 'local'].forEach(area => {
      it(`storage.${area}.clear`, () => {
        storage[area].clear()
        expect(browser.storage[area].clear.calledOnce).toBeTruthy()
      })
      it(`storage.${area}.remove`, () => {
        const key = `key-${area}`
        storage[area].remove(key)
        expect(browser.storage[area].remove.calledWith(key)).toBeTruthy()
      })
      it(`storage.${area}.get`, () => {
        const key = `key-${area}`
        storage[area].get(key)
        expect(browser.storage[area].get.calledWith(key)).toBeTruthy()
      })
      it(`storage.${area}.set`, () => {
        const key = { key: area }
        storage[area].set(key)
        expect(browser.storage[area].set.calledWith(key)).toBeTruthy()
      })
      it(`storage.${area}.addListener`, () => {
        const cb = () => {}
        storage[area].addListener(cb)
        expect(browser.storage.onChanged.addListener.calledOnce).toBeTruthy()
        expect(typeof __META__.storageListeners.get(cb).get(area)).toBe('function')
      })
      it(`storage.${area}.removeListener`, () => {
        const cb = jest.fn()
        storage[area].addListener(cb)
        storage[area].removeListener(cb)
        expect(browser.storage.onChanged.removeListener.calledOnce).toBeTruthy()
        expect(__META__.storageListeners.get(cb)).toBeUndefined()
        expect(cb).toHaveBeenCalledTimes(0)
      })
    })

    it(`storage.clear`, () => {
      storage.clear()
      expect(browser.storage.sync.clear.calledOnce).toBeTruthy()
      expect(browser.storage.local.clear.calledOnce).toBeTruthy()
    })
    it(`storage.addListener`, () => {
      const cb = jest.fn()
      storage.addListener(cb)
      expect(browser.storage.onChanged.addListener.calledOnce).toBeTruthy()
      expect(typeof __META__.storageListeners.get(cb).get('all')).toBe('function')
      expect(cb).toHaveBeenCalledTimes(0)
    })
    it(`storage.removeListener`, () => {
      const cb = jest.fn()
      storage.addListener(cb)
      storage.removeListener(cb)
      expect(browser.storage.onChanged.removeListener.calledOnce).toBeTruthy()
      expect(__META__.storageListeners.get(cb)).toBeUndefined()
      expect(cb).toHaveBeenCalledTimes(0)
    })
  })

  describe('Message', () => {
    it('message.send', () => {
      const tabId = 1
      const msg = {}

      message.send(msg)
      expect(browser.runtime.sendMessage.calledWith(msg)).toBeTruthy()
      expect(browser.tabs.sendMessage.notCalled).toBeTruthy()

      browser.runtime.sendMessage.flush()
      browser.tabs.sendMessage.flush()

      message.send(tabId, msg)
      expect(browser.tabs.sendMessage.calledWith(tabId, msg)).toBeTruthy()
      expect(browser.runtime.sendMessage.notCalled).toBeTruthy()
    })
    it('message.addListener', () => {
      const cb1 = jest.fn()
      const cb2 = jest.fn()
      let cb1Call = 0
      let cb2Call = 0
      message.addListener(cb1)
      message.addListener('MSG_1', cb2)
      expect(browser.runtime.onMessage.addListener.calledTwice).toBeTruthy()
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)
      expect(__META__.messageListeners.get(cb1)).toBeTruthy()
      expect(__META__.messageListeners.get(cb2)).toBeTruthy()

      browser.runtime.onMessage.dispatch({ type: 'MSG_2' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(++cb2Call)
    })
    it('message.removeListener', () => {
      const cb1 = jest.fn()
      const cb2 = jest.fn()
      let cb1Call = 0
      let cb2Call = 0
      message.addListener('MSG_1', cb1)
      message.addListener('MSG_2', cb2)
      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      message.removeListener('MSG_x', cb1)
      message.removeListener(cb2)
      expect(browser.runtime.onMessage.removeListener.calledTwice).toBeTruthy()
      expect(__META__.messageListeners.get(cb1)).toBeTruthy()
      expect(__META__.messageListeners.get(cb2)).toBeUndefined()

      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      browser.runtime.onMessage.dispatch({ type: 'MSG_2' })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      message.removeListener('MSG_1', cb1)
      expect(__META__.messageListeners.get(cb1)).toBeUndefined()
      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
    })

    it('message.self.initClient', () => {
      browser.runtime.sendMessage
        .withArgs({ type: '__PAGE_INFO__' })
        .returns(Promise.resolve({
          pageId: 'pageId',
          faviconURL: 'faviconURL',
          pageTitle: 'pageTitle',
          pageURL: 'pageURL',
        }))
      return message.self.initClient()
        .then(() => {
          expect(browser.runtime.sendMessage.calledWith({ type: '__PAGE_INFO__' })).toBeTruthy()
          expect(window.pageId).toBe('pageId')
          expect(window.faviconURL).toBe('faviconURL')
          expect(window.pageTitle).toBe('pageTitle')
          expect(window.pageURL).toBe('pageURL')
        })
    })
    describe('message.self.initServer', () => {
      const tab = {
        id: 1,
        favIconUrl: 'https://example.com/favIconUrl',
        url: 'https://example.com/url',
        title: 'title',
      }

      it('From tab', () => {
        message.self.initServer()
        expect(browser.runtime.onMessage.addListener.calledOnce).toBeTruthy()

        const sendResponse = jest.fn()
        browser.runtime.onMessage.dispatch({ type: '__PAGE_INFO__' }, { tab }, sendResponse)
        expect(sendResponse).toBeCalledWith(({
          pageId: tab.id,
          faviconURL: tab.favIconUrl,
          pageTitle: tab.title,
          pageURL: tab.url,
        }))
      })

      it('From browser action page', () => {
        message.self.initServer()
        expect(browser.runtime.onMessage.addListener.calledOnce).toBeTruthy()

        const sendResponse = jest.fn()
        browser.runtime.onMessage.dispatch({ type: '__PAGE_INFO__' }, {}, sendResponse)
        expect(sendResponse).toBeCalledWith(
          expect.objectContaining({
            pageId: 'popup',
          })
        )
      })

      it('Self page message transmission', () => {
        message.self.initServer()
        expect(browser.runtime.onMessage.addListener.calledOnce).toBeTruthy()

        browser.runtime.onMessage.dispatch({ type: '_&_MSG_1_&_', __pageId__: 1 }, {})
        expect(browser.runtime.sendMessage.calledWith({ type: 'MSG_1', __pageId__: 1 })).toBeTruthy()

        browser.runtime.onMessage.dispatch({ type: '_&_MSG_1_&_', __pageId__: 1 }, { tab })
        expect(browser.tabs.sendMessage.calledWith(tab.id, { type: 'MSG_1', __pageId__: 1 })).toBeTruthy()
      })
    })
    it('message.self.send', () => {
      window.pageId = 1
      message.self.send({
        type: 'MSG_1',
        prop: 'value',
      })
      expect(browser.runtime.sendMessage.calledWith({
        type: '_&_MSG_1_&_',
        __pageId__: window.pageId,
        prop: 'value',
      })).toBeTruthy()
    })
    it('message.self.addListener', () => {
      window.pageId = 1
      const cb1 = jest.fn()
      const cb2 = jest.fn()
      let cb1Call = 0
      let cb2Call = 0
      message.self.addListener(cb1)
      message.addListener(cb2)
      expect(browser.runtime.onMessage.addListener.calledTwice).toBeTruthy()
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)
      expect(__META__.messageSelfListeners.get(cb1)).toBeTruthy()
      expect(__META__.messageListeners.get(cb2)).toBeTruthy()

      browser.runtime.onMessage.dispatch({ type: 'MSG_1', __pageId__: window.pageId })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)

      browser.runtime.onMessage.dispatch({ type: 'MSG_1' })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(++cb2Call)

      browser.runtime.onMessage.dispatch({ type: 'MSG_1', __pageId__: window.pageId + 2 })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
      expect(cb2).toHaveBeenCalledTimes(cb2Call)
    })
    it('message.self.removeListener', () => {
      window.pageId = 1
      const cb1 = jest.fn()
      let cb1Call = 0
      message.self.addListener(cb1)
      browser.runtime.onMessage.dispatch({ __pageId__: window.pageId })
      expect(cb1).toHaveBeenCalledTimes(++cb1Call)

      message.self.removeListener(cb1)
      expect(browser.runtime.onMessage.removeListener.calledOnce).toBeTruthy()
      expect(__META__.messageListeners.get(cb1)).toBeUndefined()

      browser.runtime.onMessage.dispatch({ __pageId__: window.pageId })
      expect(cb1).toHaveBeenCalledTimes(cb1Call)
    })
  })
  describe('openURL', () => {
    const url = 'https://example.com'

    it('Existing tab', () => {
      browser.tabs.query.returns(Promise.resolve(
        [{
          index: 1
        }]
      ))
      return openURL(url)
        .then(() => {
          expect(browser.tabs.query.calledWith({ url })).toBeTruthy()
          expect(browser.tabs.highlight.calledWith({ tabs: 1 })).toBeTruthy()
          expect(browser.tabs.create.notCalled).toBeTruthy()
        })
    })
    it('New tab', () => {
      browser.tabs.query.returns(Promise.resolve([]))
      return openURL(url)
        .then(() => {
          expect(browser.tabs.query.calledWith({ url })).toBeTruthy()
          expect(browser.tabs.highlight.notCalled).toBeTruthy()
          expect(browser.tabs.create.calledWith({ url })).toBeTruthy()
        })
    })
  })
})
<|endoftext|>


<commit_before>var core = require('../core');
var  Device = require('ismobilejs');

// add some extra variables to the container..
Object.assign(
    core.DisplayObject.prototype,
    require('./accessibleTarget')
);

/**
 * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content.
 * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement.
 *
 * @class
 * @memberof PIXI
 * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer
 */
function AccessibilityManager(renderer)
{
	if(Device.tablet || Device.phone)
	{
		this.createTouchHook();
	}

	// first we create a div that will sit over the pixi element. This is where the div overlays will go.
    var div = document.createElement('div');

    div.style.width = 100 + 'px';
    div.style.height = 100 + 'px';
    div.style.position = 'absolute';
    div.style.top = 0;
    div.style.left = 0;
   //
    div.style.zIndex = 2;

   	/**
   	 * This is the dom element that will sit over the pixi element. This is where the div overlays will go.
   	 *
   	 * @type {HTMLElement}
   	 * @private
   	 */
   	this.div = div;

   	/**
   	 * A simple pool for storing divs.
   	 *
   	 * @type {*}
   	 * @private
   	 */
 	this.pool = [];

 	/**
 	 * This is a tick used to check if an object is no longer being rendered.
 	 *
 	 * @type {Number}
 	 * @private
 	 */
   	this.renderId = 0;

   	/**
   	 * Setting this to true will visually show the divs
   	 *
   	 * @type {boolean}
   	 */
   	this.debug = false;

  	/**
     * The renderer this accessibility manager works for.
     *
     * @member {PIXI.SystemRenderer}
     */
   	this.renderer = renderer;

   	/**
     * The array of currently active accessible items.
     *
     * @member {*[]}
     * @private
     */
   	this.children = [];

   	/**
     * pre bind the functions..
     */
   	this._onKeyDown = this._onKeyDown.bind(this);
   	this._onMouseMove = this._onMouseMove.bind(this);

   	/**
     * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false.
     *
     * @member {*[]}
     * @private
     */
   	this.isActive = false;
   	this.isMobileAccessabillity = false;

   	// let listen for tab.. once pressed we can fire up and show the accessibility layer
   	window.addEventListener('keydown', this._onKeyDown, false);
}

AccessibilityManager.prototype.constructor = AccessibilityManager;
module.exports = AccessibilityManager;

AccessibilityManager.prototype.createTouchHook = function()
{
	var hookDiv = document.createElement('button');
	hookDiv.style.width = 1 + 'px';
    hookDiv.style.height = 1 + 'px';
    hookDiv.style.position = 'absolute';
    hookDiv.style.top = -1000+'px';
    hookDiv.style.left = -1000+'px';
    hookDiv.style.zIndex = 2;
    hookDiv.style.backgroundColor = '#FF0000'
    hookDiv.title = 'HOOK DIV';

    hookDiv.addEventListener('focus', function(){

    	this.isMobileAccessabillity = true;
    	this.activate();
    	document.body.removeChild(hookDiv);

    }.bind(this));

    document.body.appendChild(hookDiv);

}

/**
 * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key
 * @private
 */
AccessibilityManager.prototype.activate = function()
{
	if(this.isActive )
	{
		return;
	}

	this.isActive = true;

	window.document.addEventListener('mousemove', this._onMouseMove, true);
	window.removeEventListener('keydown', this._onKeyDown, false);

	this.renderer.on('postrender', this.update, this);

	this.renderer.view.parentNode.appendChild(this.div);
};

/**
 * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse
 * @private
 */
AccessibilityManager.prototype.deactivate = function()
{

	if(!this.isActive || this.isMobileAccessabillity)
	{
		return;
	}

	this.isActive = false;

	window.document.removeEventListener('mousemove', this._onMouseMove);
	window.addEventListener('keydown', this._onKeyDown, false);

	this.renderer.off('postrender', this.update);

	this.div.parentNode.removeChild(this.div);

};

/**
 * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer.
 * @param displayObject {PIXI.Container} the DisplayObject to check.
 * @private
 */
AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject)
{
	if(!displayObject.visible)
	{
		return;
	}

	if(displayObject.accessible && displayObject.interactive)
	{
		if(!displayObject._accessibleActive)
		{
			this.addChild(displayObject);
		}

	   	displayObject.renderId = this.renderId;
	}

	var children = displayObject.children;

	for (var i = children.length - 1; i >= 0; i--) {

		this.updateAccessibleObjects(children[i]);
	}
};

/**
 * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects
 * @private
 */
AccessibilityManager.prototype.update = function()
{
	if(!this.renderer.renderingToScreen) {
    	return;
  	}

	// update children...
	this.updateAccessibleObjects(this.renderer._lastObjectRendered);

	var rect = this.renderer.view.getBoundingClientRect();
	var sx = rect.width  / this.renderer.width;
	var sy = rect.height / this.renderer.height;

	var div = this.div;

	div.style.left = rect.left + 'px';
	div.style.top = rect.top + 'px';
	div.style.width = this.renderer.width + 'px';
	div.style.height = this.renderer.height + 'px';

	for (var i = 0; i < this.children.length; i++)
	{

		var child = this.children[i];

		if(child.renderId !== this.renderId)
		{
			child._accessibleActive = false;

            core.utils.removeItems(this.children, i, 1);
			this.div.removeChild( child._accessibleDiv );
			this.pool.push(child._accessibleDiv);
			child._accessibleDiv = null;

			i--;

			if(this.children.length === 0)
			{
				this.deactivate();
			}
		}
		else
		{
			// map div to display..
			div = child._accessibleDiv;
			var hitArea = child.hitArea;
			var wt = child.worldTransform;

			if(child.hitArea)
			{
				div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px';
				div.style.top =  ((wt.ty + (hitArea.y * wt.d)) * sy) +  'px';

				div.style.width = (hitArea.width * wt.a * sx) + 'px';
				div.style.height = (hitArea.height * wt.d * sy) + 'px';

			}
			else
			{
				hitArea = child.getBounds();

				this.capHitArea(hitArea);

				div.style.left = (hitArea.x * sx) + 'px';
				div.style.top =  (hitArea.y * sy) +  'px';

				div.style.width = (hitArea.width * sx) + 'px';
				div.style.height = (hitArea.height * sy) + 'px';
			}
		}
	}

	// increment the render id..
	this.renderId++;
};

AccessibilityManager.prototype.capHitArea = function (hitArea)
{
    if (hitArea.x < 0)
    {
        hitArea.width += hitArea.x;
        hitArea.x = 0;
    }

    if (hitArea.y < 0)
    {
        hitArea.height += hitArea.y;
        hitArea.y = 0;
    }

    if ( hitArea.x + hitArea.width > this.renderer.width )
    {
        hitArea.width = this.renderer.width - hitArea.x;
    }

    if ( hitArea.y + hitArea.height > this.renderer.height )
    {
        hitArea.height = this.renderer.height - hitArea.y;
    }
};

/**
 * Adds a DisplayObject to the accessibility manager
 * @private
 */
AccessibilityManager.prototype.addChild = function(displayObject)
{
//	this.activate();

	var div = this.pool.pop();

	if(!div)
	{
		div = document.createElement('button');

	    div.style.width = 100 + 'px';
	    div.style.height = 100 + 'px';
	    div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent';
	    div.style.position = 'absolute';
	    div.style.zIndex = 2;
	    div.style.borderStyle = 'none';

	    div.addEventListener('click', this._onClick.bind(this));
	    div.addEventListener('focus', this._onFocus.bind(this));
	    div.addEventListener('focusout', this._onFocusOut.bind(this));
	}

	if(displayObject.accessibleTitle)
	{
		div.title = displayObject.accessibleTitle;
	}
	else if (!displayObject.accessibleTitle && !displayObject.accessibleHint)
	{
		div.title = 'displayObject ' + this.tabIndex;
	}

	if(displayObject.accessibleHint)
	{
		div.setAttribute('aria-label', displayObject.accessibleHint);
	}

	//

	displayObject._accessibleActive = true;
	displayObject._accessibleDiv = div;
	div.displayObject = displayObject;

	this.children.push(displayObject);
	this.div.appendChild( displayObject._accessibleDiv );
	displayObject._accessibleDiv.tabIndex = displayObject.tabIndex;
};

/**
 * Maps the div button press to pixi's InteractionManager (click)
 * @private
 */
AccessibilityManager.prototype._onClick = function(e)
{
	var interactionManager = this.renderer.plugins.interaction;
	interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData);
};

/**
 * Maps the div focus events to pixis InteractionManager (mouseover)
 * @private
 */
AccessibilityManager.prototype._onFocus = function(e)
{
	var interactionManager = this.renderer.plugins.interaction;
	interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData);
};

/**
 * Maps the div focus events to pixis InteractionManager (mouseout)
 * @private
 */
AccessibilityManager.prototype._onFocusOut = function(e)
{
	var interactionManager = this.renderer.plugins.interaction;
	interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData);
};

/**
 * Is called when a key is pressed
 *
 * @private
 */
AccessibilityManager.prototype._onKeyDown = function(e)
{
	if(e.keyCode !== 9)
	{
		return;
	}

	this.activate();
};

/**
 * Is called when the mouse moves across the renderer element
 *
 * @private
 */
AccessibilityManager.prototype._onMouseMove = function()
{
	this.deactivate();
};

/**
 * Destroys the accessibility manager
 *
 */
AccessibilityManager.prototype.destroy = function ()
{
	this.div = null;

	for (var i = 0; i < this.children.length; i++)
	{
		this.children[i].div = null;
	}

	window.document.removeEventListener('mousemove', this._onMouseMove);
	window.removeEventListener('keydown', this._onKeyDown);

	this.pool = null;
	this.children = null;
	this.renderer = null;

};

core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager);
core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager);
<commit_msg>semicolon, jshint<commit_after>var core = require('../core');
var  Device = require('ismobilejs');

// add some extra variables to the container..
Object.assign(
    core.DisplayObject.prototype,
    require('./accessibleTarget')
);

/**
 * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content.
 * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement.
 *
 * @class
 * @memberof PIXI
 * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer
 */
function AccessibilityManager(renderer)
{
	if(Device.tablet || Device.phone)
	{
		this.createTouchHook();
	}

	// first we create a div that will sit over the pixi element. This is where the div overlays will go.
    var div = document.createElement('div');

    div.style.width = 100 + 'px';
    div.style.height = 100 + 'px';
    div.style.position = 'absolute';
    div.style.top = 0;
    div.style.left = 0;
   //
    div.style.zIndex = 2;

   	/**
   	 * This is the dom element that will sit over the pixi element. This is where the div overlays will go.
   	 *
   	 * @type {HTMLElement}
   	 * @private
   	 */
   	this.div = div;

   	/**
   	 * A simple pool for storing divs.
   	 *
   	 * @type {*}
   	 * @private
   	 */
 	this.pool = [];

 	/**
 	 * This is a tick used to check if an object is no longer being rendered.
 	 *
 	 * @type {Number}
 	 * @private
 	 */
   	this.renderId = 0;

   	/**
   	 * Setting this to true will visually show the divs
   	 *
   	 * @type {boolean}
   	 */
   	this.debug = false;

  	/**
     * The renderer this accessibility manager works for.
     *
     * @member {PIXI.SystemRenderer}
     */
   	this.renderer = renderer;

   	/**
     * The array of currently active accessible items.
     *
     * @member {*[]}
     * @private
     */
   	this.children = [];

   	/**
     * pre bind the functions..
     */
   	this._onKeyDown = this._onKeyDown.bind(this);
   	this._onMouseMove = this._onMouseMove.bind(this);

   	/**
     * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false.
     *
     * @member {*[]}
     * @private
     */
   	this.isActive = false;
   	this.isMobileAccessabillity = false;

   	// let listen for tab.. once pressed we can fire up and show the accessibility layer
   	window.addEventListener('keydown', this._onKeyDown, false);
}

AccessibilityManager.prototype.constructor = AccessibilityManager;
module.exports = AccessibilityManager;

AccessibilityManager.prototype.createTouchHook = function()
{
	var hookDiv = document.createElement('button');
	hookDiv.style.width = 1 + 'px';
    hookDiv.style.height = 1 + 'px';
    hookDiv.style.position = 'absolute';
    hookDiv.style.top = -1000+'px';
    hookDiv.style.left = -1000+'px';
    hookDiv.style.zIndex = 2;
    hookDiv.style.backgroundColor = '#FF0000';
    hookDiv.title = 'HOOK DIV';

    hookDiv.addEventListener('focus', function(){

    	this.isMobileAccessabillity = true;
    	this.activate();
    	document.body.removeChild(hookDiv);

    }.bind(this));

    document.body.appendChild(hookDiv);

};

/**
 * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key
 * @private
 */
AccessibilityManager.prototype.activate = function()
{
	if(this.isActive )
	{
		return;
	}

	this.isActive = true;

	window.document.addEventListener('mousemove', this._onMouseMove, true);
	window.removeEventListener('keydown', this._onKeyDown, false);

	this.renderer.on('postrender', this.update, this);

	this.renderer.view.parentNode.appendChild(this.div);
};

/**
 * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse
 * @private
 */
AccessibilityManager.prototype.deactivate = function()
{

	if(!this.isActive || this.isMobileAccessabillity)
	{
		return;
	}

	this.isActive = false;

	window.document.removeEventListener('mousemove', this._onMouseMove);
	window.addEventListener('keydown', this._onKeyDown, false);

	this.renderer.off('postrender', this.update);

	this.div.parentNode.removeChild(this.div);

};

/**
 * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer.
 * @param displayObject {PIXI.Container} the DisplayObject to check.
 * @private
 */
AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject)
{
	if(!displayObject.visible)
	{
		return;
	}

	if(displayObject.accessible && displayObject.interactive)
	{
		if(!displayObject._accessibleActive)
		{
			this.addChild(displayObject);
		}

	   	displayObject.renderId = this.renderId;
	}

	var children = displayObject.children;

	for (var i = children.length - 1; i >= 0; i--) {

		this.updateAccessibleObjects(children[i]);
	}
};

/**
 * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects
 * @private
 */
AccessibilityManager.prototype.update = function()
{
	if(!this.renderer.renderingToScreen) {
    	return;
  	}

	// update children...
	this.updateAccessibleObjects(this.renderer._lastObjectRendered);

	var rect = this.renderer.view.getBoundingClientRect();
	var sx = rect.width  / this.renderer.width;
	var sy = rect.height / this.renderer.height;

	var div = this.div;

	div.style.left = rect.left + 'px';
	div.style.top = rect.top + 'px';
	div.style.width = this.renderer.width + 'px';
	div.style.height = this.renderer.height + 'px';

	for (var i = 0; i < this.children.length; i++)
	{

		var child = this.children[i];

		if(child.renderId !== this.renderId)
		{
			child._accessibleActive = false;

            core.utils.removeItems(this.children, i, 1);
			this.div.removeChild( child._accessibleDiv );
			this.pool.push(child._accessibleDiv);
			child._accessibleDiv = null;

			i--;

			if(this.children.length === 0)
			{
				this.deactivate();
			}
		}
		else
		{
			// map div to display..
			div = child._accessibleDiv;
			var hitArea = child.hitArea;
			var wt = child.worldTransform;

			if(child.hitArea)
			{
				div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px';
				div.style.top =  ((wt.ty + (hitArea.y * wt.d)) * sy) +  'px';

				div.style.width = (hitArea.width * wt.a * sx) + 'px';
				div.style.height = (hitArea.height * wt.d * sy) + 'px';

			}
			else
			{
				hitArea = child.getBounds();

				this.capHitArea(hitArea);

				div.style.left = (hitArea.x * sx) + 'px';
				div.style.top =  (hitArea.y * sy) +  'px';

				div.style.width = (hitArea.width * sx) + 'px';
				div.style.height = (hitArea.height * sy) + 'px';
			}
		}
	}

	// increment the render id..
	this.renderId++;
};

AccessibilityManager.prototype.capHitArea = function (hitArea)
{
    if (hitArea.x < 0)
    {
        hitArea.width += hitArea.x;
        hitArea.x = 0;
    }

    if (hitArea.y < 0)
    {
        hitArea.height += hitArea.y;
        hitArea.y = 0;
    }

    if ( hitArea.x + hitArea.width > this.renderer.width )
    {
        hitArea.width = this.renderer.width - hitArea.x;
    }

    if ( hitArea.y + hitArea.height > this.renderer.height )
    {
        hitArea.height = this.renderer.height - hitArea.y;
    }
};

/**
 * Adds a DisplayObject to the accessibility manager
 * @private
 */
AccessibilityManager.prototype.addChild = function(displayObject)
{
//	this.activate();

	var div = this.pool.pop();

	if(!div)
	{
		div = document.createElement('button');

	    div.style.width = 100 + 'px';
	    div.style.height = 100 + 'px';
	    div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent';
	    div.style.position = 'absolute';
	    div.style.zIndex = 2;
	    div.style.borderStyle = 'none';

	    div.addEventListener('click', this._onClick.bind(this));
	    div.addEventListener('focus', this._onFocus.bind(this));
	    div.addEventListener('focusout', this._onFocusOut.bind(this));
	}

	if(displayObject.accessibleTitle)
	{
		div.title = displayObject.accessibleTitle;
	}
	else if (!displayObject.accessibleTitle && !displayObject.accessibleHint)
	{
		div.title = 'displayObject ' + this.tabIndex;
	}

	if(displayObject.accessibleHint)
	{
		div.setAttribute('aria-label', displayObject.accessibleHint);
	}

	//

	displayObject._accessibleActive = true;
	displayObject._accessibleDiv = div;
	div.displayObject = displayObject;

	this.children.push(displayObject);
	this.div.appendChild( displayObject._accessibleDiv );
	displayObject._accessibleDiv.tabIndex = displayObject.tabIndex;
};

/**
 * Maps the div button press to pixi's InteractionManager (click)
 * @private
 */
AccessibilityManager.prototype._onClick = function(e)
{
	var interactionManager = this.renderer.plugins.interaction;
	interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData);
};

/**
 * Maps the div focus events to pixis InteractionManager (mouseover)
 * @private
 */
AccessibilityManager.prototype._onFocus = function(e)
{
	var interactionManager = this.renderer.plugins.interaction;
	interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData);
};

/**
 * Maps the div focus events to pixis InteractionManager (mouseout)
 * @private
 */
AccessibilityManager.prototype._onFocusOut = function(e)
{
	var interactionManager = this.renderer.plugins.interaction;
	interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData);
};

/**
 * Is called when a key is pressed
 *
 * @private
 */
AccessibilityManager.prototype._onKeyDown = function(e)
{
	if(e.keyCode !== 9)
	{
		return;
	}

	this.activate();
};

/**
 * Is called when the mouse moves across the renderer element
 *
 * @private
 */
AccessibilityManager.prototype._onMouseMove = function()
{
	this.deactivate();
};

/**
 * Destroys the accessibility manager
 *
 */
AccessibilityManager.prototype.destroy = function ()
{
	this.div = null;

	for (var i = 0; i < this.children.length; i++)
	{
		this.children[i].div = null;
	}

	window.document.removeEventListener('mousemove', this._onMouseMove);
	window.removeEventListener('keydown', this._onKeyDown);

	this.pool = null;
	this.children = null;
	this.renderer = null;

};

core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager);
core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager);
<|endoftext|>


<commit_before>/**
 * The extended Euclidean algorithm is an algorithm to
 * compute integers x and y such that given a and b.
 * ax + by = gcd(a, b)
 * @param  {Number} a Coefficient of x
 * @param  {Number} b Coefficient of y
 * @return {Object}   Object containing value of x, y and gcd
 */
const extendedEuclidean = (a, b) => {
  let x = 1;
  let y = 0;
  let lastx = 0;
  let lasty = 1;
  let q;
  let r;
  let m;
  let n;

  while (a !== 0) {
    q = Math.floor(b / a);
    r = b % a;

    m = lastx - (q * x);
    n = lasty - (q * y);

    lastx = x;
    lasty = y;

    x = m;
    y = n;

    b = a;
    a = r;
  }

  return {
    gcd: b,
    x: lastx,
    y: lasty
  };
};

module.exports = extendedEuclidean;
<commit_msg>Extended Euclidean: Fixing for negative integers<commit_after>/**
 * The extended Euclidean algorithm is an algorithm to
 * compute integers x and y such that given a and b.
 * ax + by = gcd(a, b)
 * @param  {Number} a Coefficient of x
 * @param  {Number} b Coefficient of y
 * @return {Object}   Object containing value of x, y and gcd
 */
const extendedEuclidean = (a, b) => {
  const terminate = 1;

  let x = 1;
  let y = 0;

  let m = 0;
  let n = 1;

  if (a === 0) {
    return { gcd: b, x, y };
  }

  if (b === 0) {
    return { gcd: b, x: m, y: n };
  }

  let q;
  let gcd;
  let finalX;
  let finalY;

  while (terminate) {
    q = Math.round(a / b);
    a %= b;

    m -= q * x;
    n -= q * y;

    if (a === 0) {
      gcd = b;
      finalX = x;
      finalY = y;

      break;
    }

    q = Math.round(b / a);
    b %= a;

    x -= q * m;
    y -= q * n;

    if (b === 0) {
      gcd = a;
      finalX = m;
      finalY = n;

      break;
    }
  }

  return {
    gcd,
    x: finalX,
    y: finalY
  };
};

module.exports = extendedEuclidean;
<|endoftext|>


<commit_before>'use strict';

angular.module('dataCampLight.directives').directive('plotsContainer', ['$window', 'BackendSessionManager', function ($window, BackendSessionManager) {

  function hexToBase64(str) {
    return btoa(String.fromCharCode.apply(null, str.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')));
  }

  // ------------------------
  // --- Exported Methods ---
  // ------------------------
  return {
    restrict: 'E',
    replace: false,
    templateUrl: 'app/views/partials/plots_container.html',
    link: function (scope, element) {
      var renderDimensions;
      var EXPAND_DIMENSIONS = {
        height: 800,
        width: 800
      };
      scope.plotIndex = 0;
      scope.plots = [];

      function clearPlotOutput() {
        scope.plotIndex = 0; //reset the plotIndex
        scope.plots.length = 0; //empty the array
        scope.currentImage = null;
      }

      function setCurrentImage() {
        scope.currentImage = scope.plots[scope.plotIndex];
        if (scope.currentImage.resize) {
          scope.currentImage.resize = false;
          BackendSessionManager.resize(renderDimensions, scope.plotIndex);
        }
      }

      /**
       * Show the previous graph
       */
      scope.previousPlot = function () {
        if (scope.plotIndex > 0) {
          scope.plotIndex--;
        }
        setCurrentImage();
      };

      /**
       * Show the next graph
       */
      scope.nextPlot = function () {
        if (scope.plotIndex < (scope.plots.length - 1)) {
          scope.plotIndex++;
        }
        setCurrentImage();
      };

      scope.removePlot = function () {
        scope.plots.splice(scope.plotIndex, 1);
        if (scope.plots.length === 0) {
          clearPlotOutput();
        }
        else {
          if (scope.plotIndex === scope.plots.length) scope.plotIndex--;
          setCurrentImage();
        }
      };

      scope.expand = function () {
        if (BackendSessionManager.expand(EXPAND_DIMENSIONS, scope.plotIndex)) {
          openExpandWindow(scope.currentImage.src);
        }
      };

      function openExpandWindow(src) {
        var expandWindow = $window.open('', '_blank', 'height=' + EXPAND_DIMENSIONS.height + 'px,width=' + EXPAND_DIMENSIONS.width + 'px');
        expandWindow.document.body.innerHTML = '

';
        if ($window.focus && expandWindow) expandWindow.focus();
      }

      function createImageSrc(img_url) {
        if (img_url.lastIndexOf("http", 0) === 0) {
          return img_url;
        }
        return 'data:image/png;base64,' + hexToBase64(img_url);
      }

      /**
       * Show a new plot
       * @param _: unused
       * @param image_url (string): the url of the new image that will be shown
       */
      scope.$on('output::plot', function (_, payload) {
        var img_url;
        if (angular.isObject(payload)) {
          img_url = payload.url;
        } else {
          // Fallback
          img_url = payload;
        }
        scope.plots.push({
          src: createImageSrc(img_url),
          resize: false
        });
        scope.plotIndex = scope.plots.length - 1;
        scope.currentImage = scope.plots[scope.plotIndex];
      });

      scope.$on('plots::resize', function (_, renderDims) {
        renderDimensions = renderDims;
        for (var i = 0; i < scope.plots.length; i++) {
          scope.plots[i].resize = true;
        }
        if (scope.plots.length > 0) {
          scope.plots[scope.plotIndex].resize = false;
          BackendSessionManager.resize(renderDimensions, scope.plotIndex);
        } else {
          BackendSessionManager.resize(renderDimensions);
        }
      });

      scope.$on('plot::resized', function (_, payload) {
        // REPLACE FIGURE
        if (payload.index < scope.plots.length) {
          scope.plots[payload.index].src = createImageSrc(payload.url);
          if (payload.index === scope.plotIndex) {
            scope.currentImage = scope.plots[scope.plotIndex];
          }
        }
      });

      scope.$on('plot::expanded', function (_, payload) {
        openExpandWindow(createImageSrc(payload.url));
      });
    }
  };
}]);
<commit_msg>Split up apply call to avoid max call stack error<commit_after>'use strict';

angular.module('dataCampLight.directives').directive('plotsContainer', ['$window', 'BackendSessionManager', function ($window, BackendSessionManager) {

  function hexToBase64(str) {
    //Generate hex array from hex string
    var hexArray = str.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ');

    var CHUNK_SIZE = 0x8000; //Arbitrary number
    var index = 0;
    var length = hexArray.length;
    var result = '';
    var slice;
    //Passing too many arguments into fromCharCode gives `Maximum call stack size exceeded`.
    //We divide the hex array into pieces and pass these.
    while (index < length) {
      slice = hexArray.slice(index, index + CHUNK_SIZE);
      result += String.fromCharCode.apply(null, slice);
      index += CHUNK_SIZE;
    }
    return btoa(result);
  }

  // ------------------------
  // --- Exported Methods ---
  // ------------------------
  return {
    restrict: 'E',
    replace: false,
    templateUrl: 'app/views/partials/plots_container.html',
    link: function (scope, element) {
      var renderDimensions;
      var EXPAND_DIMENSIONS = {
        height: 800,
        width: 800
      };
      scope.plotIndex = 0;
      scope.plots = [];

      function clearPlotOutput() {
        scope.plotIndex = 0; //reset the plotIndex
        scope.plots.length = 0; //empty the array
        scope.currentImage = null;
      }

      function setCurrentImage() {
        scope.currentImage = scope.plots[scope.plotIndex];
        if (scope.currentImage.resize) {
          scope.currentImage.resize = false;
          BackendSessionManager.resize(renderDimensions, scope.plotIndex);
        }
      }

      /**
       * Show the previous graph
       */
      scope.previousPlot = function () {
        if (scope.plotIndex > 0) {
          scope.plotIndex--;
        }
        setCurrentImage();
      };

      /**
       * Show the next graph
       */
      scope.nextPlot = function () {
        if (scope.plotIndex < (scope.plots.length - 1)) {
          scope.plotIndex++;
        }
        setCurrentImage();
      };

      scope.removePlot = function () {
        scope.plots.splice(scope.plotIndex, 1);
        if (scope.plots.length === 0) {
          clearPlotOutput();
        }
        else {
          if (scope.plotIndex === scope.plots.length) scope.plotIndex--;
          setCurrentImage();
        }
      };

      scope.expand = function () {
        if (BackendSessionManager.expand(EXPAND_DIMENSIONS, scope.plotIndex)) {
          openExpandWindow(scope.currentImage.src);
        }
      };

      function openExpandWindow(src) {
        var expandWindow = $window.open('', '_blank', 'height=' + EXPAND_DIMENSIONS.height + 'px,width=' + EXPAND_DIMENSIONS.width + 'px');
        expandWindow.document.body.innerHTML = '

';
        if ($window.focus && expandWindow) expandWindow.focus();
      }

      function createImageSrc(img_url) {
        if (img_url.lastIndexOf("http", 0) === 0) {
          return img_url;
        }
        return 'data:image/png;base64,' + hexToBase64(img_url);
      }

      /**
       * Show a new plot
       * @param _: unused
       * @param image_url (string): the url of the new image that will be shown
       */
      scope.$on('output::plot', function (_, payload) {
        var img_url;
        if (angular.isObject(payload)) {
          img_url = payload.url;
        } else {
          // Fallback
          img_url = payload;
        }
        scope.plots.push({
          src: createImageSrc(img_url),
          resize: false
        });
        scope.plotIndex = scope.plots.length - 1;
        scope.currentImage = scope.plots[scope.plotIndex];
      });

      scope.$on('plots::resize', function (_, renderDims) {
        renderDimensions = renderDims;
        for (var i = 0; i < scope.plots.length; i++) {
          scope.plots[i].resize = true;
        }
        if (scope.plots.length > 0) {
          scope.plots[scope.plotIndex].resize = false;
          BackendSessionManager.resize(renderDimensions, scope.plotIndex);
        } else {
          BackendSessionManager.resize(renderDimensions);
        }
      });

      scope.$on('plot::resized', function (_, payload) {
        // REPLACE FIGURE
        if (payload.index < scope.plots.length) {
          scope.plots[payload.index].src = createImageSrc(payload.url);
          if (payload.index === scope.plotIndex) {
            scope.currentImage = scope.plots[scope.plotIndex];
          }
        }
      });

      scope.$on('plot::expanded', function (_, payload) {
        openExpandWindow(createImageSrc(payload.url));
      });
    }
  };
}]);
<|endoftext|>