<commit_before>/**
 * Card Maker
 *
 * Author: M Habib Rohman <mhrohman@live.com>
 *
 * @class CardMaker
 */

import {
  makeElement,
  makeImage,
  colorNameToHex,
  isColor
} from './utils';

Promise.each = function(arr, fn) { // take an array and a function
  // invalid input
  if(!arr || !arr.length) return Promise.reject(new Error("Non array passed to each"));
  // empty case
  if(arr.length === 0) return Promise.resolve();
  return arr.reduce(function(prev, cur) {
    return prev.then(() => fn(cur))
  }, Promise.resolve());
}

const _config = new WeakMap();

export default class CardMaker {

  constructor(configs = {}) {

    // default config
    _config.set(this, {
      /*
       * Default align text for canvas
       */
      align: 'left',

      /*
       * Default canvas background
       */
      background: undefined,

      /*
       * Default canvas element
       */
      canvas: undefined,

      /*
       * Default text canvas color
       */
      color: 'black',

      /*
       * Default element to enabling download
       */
      download: '',

      /*
       * Default root card maker element
       */
      el: '#cardmaker',

      /*
       * Default Width for card maker.
       */
      width: 400,

      /*
       * Default Height for card maker
       */
      height: 250,

      /*
       * Template config card maker
       */
      template: {},
    });

    // set default config to global
    this.setConfig(configs);

    // put canvas to DOM
    this.putCanvas();

    // init app automatically
    this.render();

    // enable download button
    this.enableDownload();

  }

  /**
   * Change background of canvas
   *
   * @param {string} type
   * @param {object} props
   * @returns {object}
   *
   * @memberOf CardMaker
   */
  changeBackground(type, props) {
    const config = this.getConfig(['width', 'height']);
    let ctx = this.getContext();

    ctx.beginPath();

    switch(type) {
      case 'image':
        if (props.img == undefined) throw new Error('Make sure you set the background image')

        ctx.drawImage(props.img, 0, 0, config['width'], config['height']);

        break;
      case 'color':
        if (props.color == undefined) throw new Error('Make sure you set the background color')

        ctx.fillStyle = props.color;
        ctx.rect(0, 0, config['width'], config['height']);
        ctx.fill();

        break;
    }

    ctx.closePath();

    return ctx;
  }

  /**
   * Enable download
   *
   * @memberOf CardMaker
   */
  enableDownload() {
    const download = this.getConfig('download');

    // download image
    if (download.length > 0) { // Means defined
      let downloadButton = document.querySelector(download);
      if (downloadButton) {
        downloadButton.addEventListener('click', (e)=> {
          e.preventDefault();

          window.location.href = this.getImage();
        });
      } else {
        throw new Error(`Element ${ download } can't found in your DOM. Please check again, maybe you make a typo`);
      }
    }
  }

  /**
   * Get config
   *
   * @param {string} key
   * @returns {object}
   *
   * @memberOf CardMaker
   */
  getConfig(key) {
    let config = _config.get(this);

    if (key) {
      if (typeof key == 'object') {
        let result = {};

        for (let k = 0; k < key.length; k++) {
          result[key[k]] = config[key[k]];
        }

        return result;
      } else if (typeof key == 'string') {
        if (config[key] == '') throw new Error(`Config with key '${ key }' undefined, please check your key again`);

        return config[key];
      }
    } else {
      return config;
    }
  }

  /**
   * Get context 2d from canvas
   *
   * @returns
   *
   * @memberOf CardMaker
   */
  getContext() {
    if (this.getConfig('canvas')) return this.getConfig('canvas').getContext('2d');
  }

  /**
   * Get image from canvas
   *
   * @param {string} format
   * @param {float} quality
   * @returns
   *
   * @memberOf CardMaker
   */
  getImage(format = 'jpeg', quality = 1.0) {
    return this.getConfig('canvas').toDataURL(`image/${ format }`, quality);
  }

  /**
   * Make canvas element
   *
   * @param {object} props
   * @returns
   *
   * @memberOf CardMaker
   */
  makeCanvas(props = {}) {
    if (this.getConfig('canvas') != undefined) throw new Error('Cannot create canvas, You\'ve already set the canvas');

    let canvas = makeElement('canvas', props);
    this.setConfig('canvas', canvas);

    return canvas;
  }

  /**
   * Put canvas to el root
   *
   * @return {object}
   *
   * @memberOf CardMaker
   */
  putCanvas() {
    const config = this.getConfig(['el', 'width', 'height']);
    let elem, newCanvas;

    newCanvas = this.makeCanvas({
      width: config['width'],
      height: config['height']
    });

    elem = document.querySelector(config['el']);

    if (elem != null || elem != undefined) {
      return elem.appendChild(newCanvas);
    } else {
      throw new Error(`Cannot find ${ elem } element in your DOM`);
    }
  }

  /**
   * Render the app
   *
   * @returns {promises}
   *
   * @memberOf CardMaker
   */
  render() {
    let config = this.getConfig(['background', 'template']);

    // just check if background set from template
    if (!config['background'] && config['template']['background'].length > 0) this.setConfig('background', config['template']['background']);

    let actionRender = [this.renderBackground(), this.renderImage()];
    return Promise.all(actionRender).then(() => {
      this.renderText();
    });

  }

  /**
   * Render background from template
   *
   *
   * @memberOf CardMaker
   */
  renderBackground() {
    let background = this.getConfig('background');

    if (isColor(background)) {
      this.changeBackground('color', {
        color: background
      });
    } else if (background != undefined && background != '') {
      makeImage(background).then((img) => {
        this.changeBackground('image', {
          img
        });
      })
    } else {
      this.changeBackground('color', {
        color: colorNameToHex('black')
      });
      console.warn(`You don't specified background image or color, so .. we give you black background`);
    }

  }

  /**
   * Render image from template
   *
   * @memberOf CardMaker
   */
  renderImage() {
    let images = this.getConfig('template')['images'];

    // let Promise = require('bluebird');
    return Promise.each(images, (image) => {
      return makeImage(image.value).then((img) => {

        let ctx = this.getContext();
        let prop = image.props;

        setTimeout(() => {
          ctx.drawImage(img, prop.sx || 0, prop.sy || 0, prop.swidth || img.width, prop.sheight || img.height, prop.x || 0, prop.y || 0, prop.width || img.width, prop.height || img.height);
        }, 0)
      })
    }).then(() => {
      return true;
    })

  }

  /**
   * Render text from template
   *
   * @memberOf CardMaker
   */
  renderText = () => {
    let text = this.getConfig('template')['text'];

    return Promise.each(text, (val, key) => {
      setTimeout(() => {
        this.writeText(val.value, val.props);
      }, 0)
    })

  }

  /**
   * Set config
   *
   * @param {string|object} params[0]
   * @param {string} params[1]
   * @returns {object}
   *
   * @memberOf CardMaker
   */
  setConfig(...params) {
    let config = this.getConfig();

    if (params) {
      if (typeof params[0] == 'object') {
        let newConfig = params[0];
        let keys = [];

        for (let [key, val] of Object.entries(newConfig)) {
          config[key] = val;
          keys.push(key);
        }

        return this.getConfig(keys);
      } else {
        config[params[0]] = params[1];

        return this.getConfig([params[0]]);
      }
    }
  }

  /**
   * Write some text to canvas
   *
   * @return {object}
   *
   * @memberOf CardMaker
   */
  writeText(text, props = {}) {
    let ctx = this.getContext();
    let config = this.getConfig(['color', 'align']);

    if (text == '' || text == undefined ) console.warn('We\'ve found you insert an empty text, please make sure you make it valuable.');

    ctx.fillStyle = props.color || config['color'];
    ctx.textAlign = props.align || config['align'];
    ctx.font = `${ props.size || 20 }px ${ props.family || 'Arial' }`;
    ctx.fillText( text, props.x || 0 , props.y || 0 );

    return ctx;
  }

}
<commit_msg>increase delay time when render image & text<commit_after>/**
 * Card Maker
 *
 * Author: M Habib Rohman <mhrohman@live.com>
 *
 * @class CardMaker
 */

import {
  makeElement,
  makeImage,
  colorNameToHex,
  isColor
} from './utils';

Promise.each = function(arr, fn) { // take an array and a function
  // invalid input
  if(!arr || !arr.length) return Promise.reject(new Error("Non array passed to each"));
  // empty case
  if(arr.length === 0) return Promise.resolve();
  return arr.reduce(function(prev, cur) {
    return prev.then(() => fn(cur))
  }, Promise.resolve());
}

const _config = new WeakMap();

export default class CardMaker {

  constructor(configs = {}) {

    // default config
    _config.set(this, {
      /*
       * Default align text for canvas
       */
      align: 'left',

      /*
       * Default canvas background
       */
      background: undefined,

      /*
       * Default canvas element
       */
      canvas: undefined,

      /*
       * Default text canvas color
       */
      color: 'black',

      /*
       * Default element to enabling download
       */
      download: '',

      /*
       * Default root card maker element
       */
      el: '#cardmaker',

      /*
       * Default Width for card maker.
       */
      width: 400,

      /*
       * Default Height for card maker
       */
      height: 250,

      /*
       * Template config card maker
       */
      template: {},
    });

    // set default config to global
    this.setConfig(configs);

    // put canvas to DOM
    this.putCanvas();

    // init app automatically
    this.render();

    // enable download button
    this.enableDownload();

  }

  /**
   * Change background of canvas
   *
   * @param {string} type
   * @param {object} props
   * @returns {object}
   *
   * @memberOf CardMaker
   */
  changeBackground(type, props) {
    const config = this.getConfig(['width', 'height']);
    let ctx = this.getContext();

    ctx.beginPath();

    switch(type) {
      case 'image':
        if (props.img == undefined) throw new Error('Make sure you set the background image')

        ctx.drawImage(props.img, 0, 0, config['width'], config['height']);

        break;
      case 'color':
        if (props.color == undefined) throw new Error('Make sure you set the background color')

        ctx.fillStyle = props.color;
        ctx.rect(0, 0, config['width'], config['height']);
        ctx.fill();

        break;
    }

    ctx.closePath();

    return ctx;
  }

  /**
   * Enable download
   *
   * @memberOf CardMaker
   */
  enableDownload() {
    const download = this.getConfig('download');

    // download image
    if (download.length > 0) { // Means defined
      let downloadButton = document.querySelector(download);
      if (downloadButton) {
        downloadButton.addEventListener('click', (e)=> {
          e.preventDefault();

          window.location.href = this.getImage();
        });
      } else {
        throw new Error(`Element ${ download } can't found in your DOM. Please check again, maybe you make a typo`);
      }
    }
  }

  /**
   * Get config
   *
   * @param {string} key
   * @returns {object}
   *
   * @memberOf CardMaker
   */
  getConfig(key) {
    let config = _config.get(this);

    if (key) {
      if (typeof key == 'object') {
        let result = {};

        for (let k = 0; k < key.length; k++) {
          result[key[k]] = config[key[k]];
        }

        return result;
      } else if (typeof key == 'string') {
        if (config[key] == '') throw new Error(`Config with key '${ key }' undefined, please check your key again`);

        return config[key];
      }
    } else {
      return config;
    }
  }

  /**
   * Get context 2d from canvas
   *
   * @returns
   *
   * @memberOf CardMaker
   */
  getContext() {
    if (this.getConfig('canvas')) return this.getConfig('canvas').getContext('2d');
  }

  /**
   * Get image from canvas
   *
   * @param {string} format
   * @param {float} quality
   * @returns
   *
   * @memberOf CardMaker
   */
  getImage(format = 'jpeg', quality = 1.0) {
    return this.getConfig('canvas').toDataURL(`image/${ format }`, quality);
  }

  /**
   * Make canvas element
   *
   * @param {object} props
   * @returns
   *
   * @memberOf CardMaker
   */
  makeCanvas(props = {}) {
    if (this.getConfig('canvas') != undefined) throw new Error('Cannot create canvas, You\'ve already set the canvas');

    let canvas = makeElement('canvas', props);
    this.setConfig('canvas', canvas);

    return canvas;
  }

  /**
   * Put canvas to el root
   *
   * @return {object}
   *
   * @memberOf CardMaker
   */
  putCanvas() {
    const config = this.getConfig(['el', 'width', 'height']);
    let elem, newCanvas;

    newCanvas = this.makeCanvas({
      width: config['width'],
      height: config['height']
    });

    elem = document.querySelector(config['el']);

    if (elem != null || elem != undefined) {
      return elem.appendChild(newCanvas);
    } else {
      throw new Error(`Cannot find ${ elem } element in your DOM`);
    }
  }

  /**
   * Render the app
   *
   * @returns {promises}
   *
   * @memberOf CardMaker
   */
  render() {
    let config = this.getConfig(['background', 'template']);

    // just check if background set from template
    if (!config['background'] && config['template']['background'].length > 0) this.setConfig('background', config['template']['background']);

    let actionRender = [this.renderBackground(), this.renderImage()];
    return Promise.all(actionRender).then(() => {
      this.renderText();
    });

  }

  /**
   * Render background from template
   *
   *
   * @memberOf CardMaker
   */
  renderBackground() {
    let background = this.getConfig('background');

    if (isColor(background)) {
      this.changeBackground('color', {
        color: background
      });
    } else if (background != undefined && background != '') {
      makeImage(background).then((img) => {
        this.changeBackground('image', {
          img
        });
      })
    } else {
      this.changeBackground('color', {
        color: colorNameToHex('black')
      });
      console.warn(`You don't specified background image or color, so .. we give you black background`);
    }

  }

  /**
   * Render image from template
   *
   * @memberOf CardMaker
   */
  renderImage() {
    let images = this.getConfig('template')['images'];

    // let Promise = require('bluebird');
    return Promise.each(images, (image) => {
      return makeImage(image.value).then((img) => {

        let ctx = this.getContext();
        let prop = image.props;

        setTimeout(() => {
          ctx.drawImage(img, prop.sx || 0, prop.sy || 0, prop.swidth || img.width, prop.sheight || img.height, prop.x || 0, prop.y || 0, prop.width || img.width, prop.height || img.height);
        }, 1)
      })
    }).then(() => {
      return true;
    })

  }

  /**
   * Render text from template
   *
   * @memberOf CardMaker
   */
  renderText = () => {
    let text = this.getConfig('template')['text'];

    return Promise.each(text, (val, key) => {
      setTimeout(() => {
        this.writeText(val.value, val.props);
      }, 1)
    })

  }

  /**
   * Set config
   *
   * @param {string|object} params[0]
   * @param {string} params[1]
   * @returns {object}
   *
   * @memberOf CardMaker
   */
  setConfig(...params) {
    let config = this.getConfig();

    if (params) {
      if (typeof params[0] == 'object') {
        let newConfig = params[0];
        let keys = [];

        for (let [key, val] of Object.entries(newConfig)) {
          config[key] = val;
          keys.push(key);
        }

        return this.getConfig(keys);
      } else {
        config[params[0]] = params[1];

        return this.getConfig([params[0]]);
      }
    }
  }

  /**
   * Write some text to canvas
   *
   * @return {object}
   *
   * @memberOf CardMaker
   */
  writeText(text, props = {}) {
    let ctx = this.getContext();
    let config = this.getConfig(['color', 'align']);

    if (text == '' || text == undefined ) console.warn('We\'ve found you insert an empty text, please make sure you make it valuable.');

    ctx.fillStyle = props.color || config['color'];
    ctx.textAlign = props.align || config['align'];
    ctx.font = `${ props.size || 20 }px ${ props.family || 'Arial' }`;
    ctx.fillText( text, props.x || 0 , props.y || 0 );

    return ctx;
  }

}
<|endoftext|>


<commit_before>var defaults = require('defaults-es6');
var pathToRegexp = require('path-to-regexp');
var findParent = require('find-parent');
var defaultOpts = {};
const { mix } = require('@weddell/mixwith');
var EventEmitterMixin = require('@weddell/event-emitter-mixin');

function matchPattern(pattern, parentMatched, pathName, fullPath, end) {
    var params = [];

    if (pattern.charAt(0) !== '/') {
        if (parentMatched) {
            var regex = pathToRegexp('/' + pattern, params, { end });
            var routePathname = pathName;
            var routeFullPath = fullPath;
            var match = regex.exec(routePathname);
        }
    } else {
        regex = pathToRegexp(pattern, params, { end });
        routePathname = fullPath;
        routeFullPath = routePathname;
        match = regex.exec(routePathname);
    }

    return { params, match, fullPath: routeFullPath, pathName: routePathname, regex };
}

class BaseRouter { }

class Router extends mix(BaseRouter).with(EventEmitterMixin) {

    constructor(opts) {
        opts = defaults(opts, defaultOpts);
        super(opts);
        this.currentRoute = null;
        this.routes = [];
        this.promise = null;
        this.onRoute = opts.onRoute;
        this._isInit = false;
        if (opts.routes) {
            this.addRoutes(opts.routes);
        }
    }

    awaitFirstRoute() {
        return this.currentRoute ? Promise.resolve() : new Promise(resolve => this.once('route', resolve))
    }

    async route(pathName, shouldReplaceState = false, triggeringEvent = null) {
        if (typeof shouldReplaceState !== 'boolean') {
            triggeringEvent = shouldReplaceState;
            shouldReplaceState = false
        }
        if (typeof pathName === 'string') {
            var matches = this.matchRoute(pathName, this.routes);
        } else if (Array.isArray(pathName)) {
            matches = pathName;
        } else if (pathName) {
            //assuming an object was passed to route by named route.
            if (!pathName.name && !this.currentRoute) {
                await this.awaitFirstRoute()
            }
            var matches = this.compileRouterLink(pathName);
            if (matches) {
                return this.route(matches.fullPath + (pathName.hash ? '#' + pathName.hash : ''), shouldReplaceState, triggeringEvent);
            }
        }
        if (matches) {
            var hash = matches.hash;
            Object.assign(matches, { triggeringEvent });
            var isInitialRoute = !this.currentRoute;

            if (this.currentRoute && matches.fullPath === this.currentRoute.fullPath && this.currentRoute.hash === matches.hash) {
                var promise = Promise.resolve(Object.assign(matches, { isCurrentRoute: true }))
                    .then(matches => {
                        if (shouldReplaceState) {
                            return this.replaceState(matches.fullPath, hash);
                        } else {
                            return this.pushState(matches.fullPath, hash);
                        }
                    });
            } else {
                promise = Promise.all(matches.map((currMatch, key) => {
                    if (key === matches.length - 1 && currMatch.route.redirect) {
                        if (typeof currMatch.route.redirect === 'function') {
                            var redirectPath = currMatch.route.redirect.call(this, matches);
                        } else {
                            //assuming string - path
                            redirectPath = currMatch.route.redirect;
                        }
                        if (redirectPath === matches.fullPath) throw "Redirect loop detected: '" + redirectPath + "'";

                        return Promise.reject(redirectPath);
                    }

                    return Promise.resolve(typeof currMatch.route.handler == 'function' ? currMatch.route.handler.call(this, matches) : currMatch.route.handler);
                }))
                    .then(results => {
                        return Promise.resolve(this.onRoute ? this.onRoute.call(this, matches, results.filter(val => val)) : null)
                            .then(() => matches)
                            .then(matches => {
                                if (isInitialRoute || shouldReplaceState) {
                                    this.replaceState(matches.fullPath, hash, matches.isRouteUpdate && matches.keepUpdateScrollPos ? null : { x: 0, y: 0 });
                                } else if (!matches.isCurrentRoute) {
                                    return this.pushState(matches.fullPath, hash, matches.isRouteUpdate && matches.keepUpdateScrollPos ? null : { x: 0, y: 0 })
                                        .then(() => matches);
                                }
                                return matches;
                            });
                    }, redirectPath => {
                        return this.route(redirectPath, true, triggeringEvent)
                    });
                this.currentRoute = matches;
            }
            return this.promise = promise.then(result => {
                this.promise = null
                this.trigger('route');
                return result;
            });
        }
        return this.promise = null;
    }

    awaitRoute() {
        return this.promise ? this.promise : Promise.resolve();
    }

    static getNamedRoute(name, routes, currPath) {
        if (!name) return null;
        if (!currPath) currPath = [];
        var matchedRoute = null;
        routes.every(route => {
            matchedRoute = route.name === name ? route : matchedRoute;
            if (!matchedRoute && route.children) {
                matchedRoute = this.getNamedRoute(name, route.children, currPath.concat(route));
            }
            return !matchedRoute;
        });
        if (matchedRoute) {
            matchedRoute = Object.assign({ route: matchedRoute }, matchedRoute);
            matchedRoute = Object.assign(currPath.concat(matchedRoute.route), matchedRoute);
        }
        return matchedRoute || null;
    }

    matchRoute(pathName, routes, routePath, fullPath, parentMatched) {
        if (!routePath) routePath = [];
        var result = null;
        var hashIndex = pathName.indexOf('#');
        var hash = hashIndex > -1 ? pathName.slice(hashIndex + 1) : '';
        pathName = hashIndex > -1 ? pathName.slice(0, hashIndex) : pathName;

        if (typeof pathName !== 'string') {
            return null;
        }

        if (typeof fullPath === 'undefined') {
            fullPath = pathName;
        }

        if (fullPath.charAt(0) !== '/' && this.currentRoute) {
            fullPath = this.currentRoute.fullPath + fullPath;
        }

        routes.every((currRoute) => {
            var params = [];

            var currMatch = matchPattern(currRoute.pattern, parentMatched, pathName, fullPath, false);

            var newPath = routePath.concat({ route: currRoute, match: currMatch.match, params: currMatch.params });

            if (currRoute.children) {
                result = this.matchRoute(currMatch.pathName.replace(currMatch.regex, ''), currRoute.children, newPath, currMatch.fullPath, !!currMatch.match);
            }

            if (!result) {
                currMatch = matchPattern(currRoute.pattern, parentMatched, pathName, fullPath, true);
                var matchObj = { route: currRoute, match: currMatch.match, params: currMatch.params };
                var isValid = true;
                if (currRoute.validator) {
                    isValid = currRoute.validator.call(currRoute, matchObj);
                }
                result = currMatch.match && isValid ? routePath.concat(matchObj) : null;
            }

            if (result) {
                result.paramVals = result.reduce((paramsObj, routeObj) => {
                    routeObj.params.forEach((param, key) => {
                        if (routeObj.match) {
                            paramsObj[param.name] = routeObj.match[key + 1];
                        }
                    });
                    return paramsObj;
                }, {});

                result.route = result[result.length - 1].route;
                result.fullPath = fullPath;
                result.hash = hash;
            }

            return !result;
        });

        if (result) {
            result.isRouteUpdate = !!(this.currentRoute && result.route.name === this.currentRoute.route.name);
            result.keepUpdateScrollPos = result.isRouteUpdate && !!(typeof result.route.keepUpdateScrollPos === 'function' ?
                (result.route.keepUpdateScrollPos.call(this, { newRoute: result, prevRoute: this.currentRoute })) :
                result.route.keepUpdateScrollPos);
        }

        return result;
    }

    addRoutes(routes) {
        this.routes = this.routes.concat(routes);
    }

    compileRouterLink(obj) {
        /*
       * Takes an object specifying a router name and params, returns an object with compiled path and matched route
       */
        if (typeof obj === 'string') return obj;
        var paramDefaults = {};
        var routeName;

        if (this.currentRoute) {
            routeName = this.currentRoute.route.name;

            paramDefaults = this.currentRoute.reduce((params, currRoute) => {
                currRoute.params.forEach((param, key) => {
                    var val = currRoute.match[key + 1];
                    if (typeof val !== 'undefined') {
                        params[param.name] = val;
                    }
                })
                return params;
            }, paramDefaults);
        }

        routeName = obj.name ? obj.name : routeName;
        obj.params = Object.assign(paramDefaults, obj.params);
        var hash = obj.hash;

        var route = Router.getNamedRoute(routeName, this.routes);

        if (route) {
            try {
                var pattern = route.reduce((finalPath, pathRoute) => {
                    var segment = pathRoute.pattern;
                    return pathRoute.pattern.charAt(0) === '/' ? segment : finalPath + segment;
                }, '');

                var fullPath = pathToRegexp.compile(pattern)(obj.params);
            } catch (err) {
                throw "Encountered error trying to build router link: " + err.toString();
            }
            var matches = [{
                fullPath,
                route,
                match: null
            }];
            matches.hash = hash;
            matches.route = route;
            matches.fullPath = fullPath[0] !== '/' ? '/' + fullPath : fullPath;
            return matches;
        } else {
            console.warn('could not find route with name', routeName);
        }
        return null;
    }

    init(initPath) {
        if (!this._isInit && this.routes) {
            if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
            this._isInit = true;

            addEventListener('popstate', this.onPopState.bind(this));
            addEventListener('hashchange', this.onHashChange.bind(this));

            document.body.addEventListener('click', (evt) => {
                var clickedATag = findParent.byMatcher(evt.target, el => el.tagName === 'A');
                if (clickedATag) {
                    var href = clickedATag.getAttribute('href');
                    if (href) {
                        var result = this.route(href, evt);
                        if (result) {
                            evt.preventDefault();
                            this.replaceState(location.pathname, location.hash, { x: window.pageXOffset, y: window.pageYOffset });
                        }
                    }
                }
            });
            return this.route(initPath || (location.pathname + location.hash));
        }
        return Promise.resolve();
    }

    pushState(pathName, hash, scrollPos={ x: window.pageXOffset, y: window.pageYOffset }) {
        if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
        if (pathName.charAt(pathName.length - 1) !== '/') pathName = pathName + '/';

        if (typeof hash !== 'string') {
            hash = '';
        }

        return new Promise((resolve) => {
            var setListener = false;
            var off;
            var pushState = evt => {
                if (setListener) {
                    off();
                    history.replaceState({ fullPath: pathName, hash, scrollPos, isWeddellState: true }, document.title, location.origin + pathName + location.search + (hash || ''));
                    this.setScrollPos(scrollPos, hash);
                } else {
                    history.pushState({ fullPath: pathName, hash, scrollPos, isWeddellState: true }, document.title, location.origin + pathName + location.search + (hash || ''));
                    this.setScrollPos(scrollPos, hash);
                }

                resolve();
            }
            if (location.hash === hash) {
                pushState()
            } else {
                setListener = true;
                off = this.on('hashchange', pushState);
                location.hash = hash;
            }
        })
    }

    replaceState(pathName, hash, scrollPos) {
        if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
        if (pathName.charAt(pathName.length - 1) !== '/') pathName = pathName + '/';

        if (!history.state || !history.state.isWeddellState || history.state.fullPath !== pathName || history.state.hash !== hash || (scrollPos && (history.state.x !== scrollPos.x || history.state.y !== scrollPos.y))) {
            history.replaceState({ fullPath: pathName, hash, scrollPos: scrollPos || (history.state && history.state.scrollPos) || { x: window.pageXOffset, y: window.pageYOffset }, isWeddellState: true }, document.title, location.origin + pathName + location.search + (hash || ''));
        }

        this.setScrollPos(scrollPos, hash);
    }

    onHashChange(evt) {
        if (!history.state) {
            this.replaceState(location.pathname, location.hash, { x: window.pageXOffset, y: window.pageYOffset });
        }
        this.trigger('hashchange')
    }

    setScrollPos(scrollPos, hash) {
        if (hash) {
            var el;
            try {
                el = document.querySelector(hash);
            } catch (err) { }

            if (el) {
                var rect = el.getBoundingClientRect();
                window.scrollTo(rect.left + window.pageXOffset, rect.top + window.pageYOffset);
            }
        } else if (scrollPos) {
            window.scrollTo(scrollPos.x, scrollPos.y);
        }
    }

    onPopState(evt) {
        //@TODO paging forward does not restore scroll position due to lack of available hook to capture it. we may at some point want to capture it in a scroll event.
        var state = history.state;
        if (evt && evt.state && evt.state.isWeddellState === true) {
            var result = this.route(evt.state.fullPath + (evt.state.hash || ''), true, evt);
            if (result && evt.state.scrollPos) {
                if (result.then) {
                    result
                        .then(matches => {
                            window.scrollTo(evt.state.scrollPos.x, evt.state.scrollPos.y)
                        })
                } else {
                    window.scrollTo(evt.state.scrollPos.x, evt.state.scrollPos.y);
                }
            }
        }
    }
}

module.exports = Router;
<commit_msg>fix router issue{<commit_after>var defaults = require('defaults-es6');
var pathToRegexp = require('path-to-regexp');
var findParent = require('find-parent');
var defaultOpts = {};
const { mix } = require('@weddell/mixwith');
var EventEmitterMixin = require('@weddell/event-emitter-mixin');

function matchPattern(pattern, parentMatched, pathName, fullPath, end) {
    var params = [];

    if (pattern.charAt(0) !== '/') {
        if (parentMatched) {
            var regex = pathToRegexp('/' + pattern, params, { end });
            var routePathname = pathName;
            var routeFullPath = fullPath;
            var match = regex.exec(routePathname);
        }
    } else {
        regex = pathToRegexp(pattern, params, { end });
        routePathname = fullPath;
        routeFullPath = routePathname;
        match = regex.exec(routePathname);
    }

    return { params, match, fullPath: routeFullPath, pathName: routePathname, regex };
}

class BaseRouter { }

class Router extends mix(BaseRouter).with(EventEmitterMixin) {

    constructor(opts) {
        opts = defaults(opts, defaultOpts);
        super(opts);
        this.currentRoute = null;
        this.routes = [];
        this.promise = null;
        this.onRoute = opts.onRoute;
        this._isInit = false;
        if (opts.routes) {
            this.addRoutes(opts.routes);
        }
    }

    awaitFirstRoute() {
        return this.currentRoute ? Promise.resolve() : new Promise(resolve => this.once('route', resolve))
    }

    route(pathName, shouldReplaceState = false, triggeringEvent = null) {
        if (typeof shouldReplaceState !== 'boolean') {
            triggeringEvent = shouldReplaceState;
            shouldReplaceState = false
        }
        if (typeof pathName === 'string') {
            var matches = this.matchRoute(pathName, this.routes);
        } else if (Array.isArray(pathName)) {
            matches = pathName;
        } else if (pathName) {
            //assuming an object was passed to route by named route.
            if (!pathName.name && !this.currentRoute) {
                throw new Error(`Unable to route to unnamed object route because no routing event has occurred yet. Try awaiting router.awaitFirstRoute() before caling router.route.`);
            }
            matches = this.compileRouterLink(pathName);
            if (matches) {
                return this.route(matches.fullPath + (pathName.hash ? '#' + pathName.hash : ''), shouldReplaceState, triggeringEvent);
            }
        }
        if (matches) {
            return this.promise = (this.promise || Promise.resolve())
                .then(() => {
                    var hash = matches.hash;
                    Object.assign(matches, { triggeringEvent });
                    var isInitialRoute = !this.currentRoute;

                    if (this.currentRoute && matches.fullPath === this.currentRoute.fullPath && this.currentRoute.hash === matches.hash) {
                        var promise = Promise.resolve(Object.assign(matches, { isCurrentRoute: true }))
                            .then(matches => {
                                if (shouldReplaceState) {
                                    return this.replaceState(matches.fullPath, hash);
                                } else {
                                    return this.pushState(matches.fullPath, hash);
                                }
                            });
                    } else {
                        promise = Promise.all(matches.map((currMatch, key) => {
                            if (key === matches.length - 1 && currMatch.route.redirect) {
                                if (typeof currMatch.route.redirect === 'function') {
                                    var redirectPath = currMatch.route.redirect.call(this, matches);
                                } else {
                                    //assuming string - path
                                    redirectPath = currMatch.route.redirect;
                                }
                                if (redirectPath === matches.fullPath) throw "Redirect loop detected: '" + redirectPath + "'";

                                return Promise.reject(redirectPath);
                            }

                            return Promise.resolve(typeof currMatch.route.handler == 'function' ? currMatch.route.handler.call(this, matches) : currMatch.route.handler);
                        }))
                            .then(results => {
                                return Promise.resolve(this.onRoute ? this.onRoute.call(this, matches, results.filter(val => val)) : null)
                                    .then(() => matches)
                                    .then(matches => {
                                        if (isInitialRoute || shouldReplaceState) {
                                            this.replaceState(matches.fullPath, hash, matches.isRouteUpdate && matches.keepUpdateScrollPos ? null : { x: 0, y: 0 });
                                        } else if (!matches.isCurrentRoute) {
                                            return this.pushState(matches.fullPath, hash, matches.isRouteUpdate && matches.keepUpdateScrollPos ? null : { x: 0, y: 0 })
                                                .then(() => matches);
                                        }
                                        return matches;
                                    });
                            }, redirectPath => {
                                return this.route(redirectPath, true, triggeringEvent)
                            });
                        this.currentRoute = matches;
                    }
                    return promise;
                })
                .then(result => {
                    this.promise = null
                    this.trigger('route');
                    return result;
                });
        }
        return null;
    }

    awaitRoute() {
        return this.promise ? this.promise : Promise.resolve();
    }

    static getNamedRoute(name, routes, currPath) {
        if (!name) return null;
        if (!currPath) currPath = [];
        var matchedRoute = null;
        routes.every(route => {
            matchedRoute = route.name === name ? route : matchedRoute;
            if (!matchedRoute && route.children) {
                matchedRoute = this.getNamedRoute(name, route.children, currPath.concat(route));
            }
            return !matchedRoute;
        });
        if (matchedRoute) {
            matchedRoute = Object.assign({ route: matchedRoute }, matchedRoute);
            matchedRoute = Object.assign(currPath.concat(matchedRoute.route), matchedRoute);
        }
        return matchedRoute || null;
    }

    matchRoute(pathName, routes, routePath, fullPath, parentMatched) {
        if (!routePath) routePath = [];
        var result = null;
        var hashIndex = pathName.indexOf('#');
        var hash = hashIndex > -1 ? pathName.slice(hashIndex + 1) : '';
        pathName = hashIndex > -1 ? pathName.slice(0, hashIndex) : pathName;

        if (typeof pathName !== 'string') {
            return null;
        }

        if (typeof fullPath === 'undefined') {
            fullPath = pathName;
        }

        if (fullPath.charAt(0) !== '/' && this.currentRoute) {
            fullPath = this.currentRoute.fullPath + fullPath;
        }

        routes.every((currRoute) => {
            var params = [];

            var currMatch = matchPattern(currRoute.pattern, parentMatched, pathName, fullPath, false);

            var newPath = routePath.concat({ route: currRoute, match: currMatch.match, params: currMatch.params });

            if (currRoute.children) {
                result = this.matchRoute(currMatch.pathName.replace(currMatch.regex, ''), currRoute.children, newPath, currMatch.fullPath, !!currMatch.match);
            }

            if (!result) {
                currMatch = matchPattern(currRoute.pattern, parentMatched, pathName, fullPath, true);
                var matchObj = { route: currRoute, match: currMatch.match, params: currMatch.params };
                var isValid = true;
                if (currRoute.validator) {
                    isValid = currRoute.validator.call(currRoute, matchObj);
                }
                result = currMatch.match && isValid ? routePath.concat(matchObj) : null;
            }

            if (result) {
                result.paramVals = result.reduce((paramsObj, routeObj) => {
                    routeObj.params.forEach((param, key) => {
                        if (routeObj.match) {
                            paramsObj[param.name] = routeObj.match[key + 1];
                        }
                    });
                    return paramsObj;
                }, {});

                result.route = result[result.length - 1].route;
                result.fullPath = fullPath;
                result.hash = hash;
            }

            return !result;
        });

        if (result) {
            result.isRouteUpdate = !!(this.currentRoute && result.route.name === this.currentRoute.route.name);
            result.keepUpdateScrollPos = result.isRouteUpdate && !!(typeof result.route.keepUpdateScrollPos === 'function' ?
                (result.route.keepUpdateScrollPos.call(this, { newRoute: result, prevRoute: this.currentRoute })) :
                result.route.keepUpdateScrollPos);
        }

        return result;
    }

    addRoutes(routes) {
        this.routes = this.routes.concat(routes);
    }

    compileRouterLink(obj) {
        /*
       * Takes an object specifying a router name and params, returns an object with compiled path and matched route
       */
        if (typeof obj === 'string') return obj;
        var paramDefaults = {};
        var routeName;

        if (this.currentRoute) {
            routeName = this.currentRoute.route.name;

            paramDefaults = this.currentRoute.reduce((params, currRoute) => {
                currRoute.params.forEach((param, key) => {
                    var val = currRoute.match[key + 1];
                    if (typeof val !== 'undefined') {
                        params[param.name] = val;
                    }
                })
                return params;
            }, paramDefaults);
        }

        routeName = obj.name ? obj.name : routeName;
        obj.params = Object.assign(paramDefaults, obj.params);
        var hash = obj.hash;

        var route = Router.getNamedRoute(routeName, this.routes);

        if (route) {
            try {
                var pattern = route.reduce((finalPath, pathRoute) => {
                    var segment = pathRoute.pattern;
                    return pathRoute.pattern.charAt(0) === '/' ? segment : finalPath + segment;
                }, '');

                var fullPath = pathToRegexp.compile(pattern)(obj.params);
            } catch (err) {
                throw "Encountered error trying to build router link: " + err.toString();
            }
            var matches = [{
                fullPath,
                route,
                match: null
            }];
            matches.hash = hash;
            matches.route = route;
            matches.fullPath = fullPath[0] !== '/' ? '/' + fullPath : fullPath;
            return matches;
        } else {
            console.warn('could not find route with name', routeName);
        }
        return null;
    }

    init(initPath) {
        if (!this._isInit && this.routes) {
            if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
            this._isInit = true;

            addEventListener('popstate', this.onPopState.bind(this));
            addEventListener('hashchange', this.onHashChange.bind(this));

            document.body.addEventListener('click', (evt) => {
                var clickedATag = findParent.byMatcher(evt.target, el => el.tagName === 'A');
                if (clickedATag) {
                    var href = clickedATag.getAttribute('href');
                    if (href) {
                        var result = this.route(href, evt);
                        if (result) {
                            evt.preventDefault();
                            this.replaceState(location.pathname, location.hash, { x: window.pageXOffset, y: window.pageYOffset });
                        }
                    }
                }
            });
            return this.route(initPath || (location.pathname + location.hash));
        }
        return Promise.resolve();
    }

    pushState(pathName, hash, scrollPos={ x: window.pageXOffset, y: window.pageYOffset }) {
        if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
        if (pathName.charAt(pathName.length - 1) !== '/') pathName = pathName + '/';

        if (typeof hash !== 'string') {
            hash = '';
        }

        return new Promise((resolve) => {
            var setListener = false;
            var off;
            var pushState = evt => {
                if (setListener) {
                    off();
                    history.replaceState({ fullPath: pathName, hash, scrollPos, isWeddellState: true }, document.title, location.origin + pathName + location.search + (hash || ''));
                    this.setScrollPos(scrollPos, hash);
                } else {
                    history.pushState({ fullPath: pathName, hash, scrollPos, isWeddellState: true }, document.title, location.origin + pathName + location.search + (hash || ''));
                    this.setScrollPos(scrollPos, hash);
                }

                resolve();
            }
            if (location.hash === hash) {
                pushState()
            } else {
                setListener = true;
                off = this.on('hashchange', pushState);
                location.hash = hash;
            }
        })
    }

    replaceState(pathName, hash, scrollPos) {
        if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
        if (pathName.charAt(pathName.length - 1) !== '/') pathName = pathName + '/';

        if (!history.state || !history.state.isWeddellState || history.state.fullPath !== pathName || history.state.hash !== hash || (scrollPos && (history.state.x !== scrollPos.x || history.state.y !== scrollPos.y))) {
            history.replaceState({ fullPath: pathName, hash, scrollPos: scrollPos || (history.state && history.state.scrollPos) || { x: window.pageXOffset, y: window.pageYOffset }, isWeddellState: true }, document.title, location.origin + pathName + location.search + (hash || ''));
        }

        this.setScrollPos(scrollPos, hash);
    }

    onHashChange(evt) {
        if (!history.state) {
            this.replaceState(location.pathname, location.hash, { x: window.pageXOffset, y: window.pageYOffset });
        }
        this.trigger('hashchange')
    }

    setScrollPos(scrollPos, hash) {
        if (hash) {
            var el;
            try {
                el = document.querySelector(hash);
            } catch (err) { }

            if (el) {
                var rect = el.getBoundingClientRect();
                window.scrollTo(rect.left + window.pageXOffset, rect.top + window.pageYOffset);
            }
        } else if (scrollPos) {
            window.scrollTo(scrollPos.x, scrollPos.y);
        }
    }

    onPopState(evt) {
        //@TODO paging forward does not restore scroll position due to lack of available hook to capture it. we may at some point want to capture it in a scroll event.
        var state = history.state;
        if (evt && evt.state && evt.state.isWeddellState === true) {
            var result = this.route(evt.state.fullPath + (evt.state.hash || ''), true, evt);
            if (result && evt.state.scrollPos) {
                if (result.then) {
                    result
                        .then(matches => {
                            window.scrollTo(evt.state.scrollPos.x, evt.state.scrollPos.y)
                        })
                } else {
                    window.scrollTo(evt.state.scrollPos.x, evt.state.scrollPos.y);
                }
            }
        }
    }
}

module.exports = Router;
<|endoftext|>


<commit_before>/*!
 * dopamine.js
 *
 * Copyright 2011, Armin Dressler
 * Licensed under the MIT license.
 * See LICENSE for details.
 */
;(function($) {

	var default_options = {
		enable: true,
		accept: [],
		ignore: []
	}

	var Dopamine = function() {
		this.init.apply(this, arguments);
	}

	Dopamine.prototype = {

		name: 'Dopamine',

		init: function(options) {
			options = options || {};

			this.plugins = [];
			this.options = $.extend({}, default_options, options);
		},

		bind: function(type, func) {
			$(this).bind(type, func);
		},

		unbind: function(type, func) {
			$(this).unbind(type, func);
		},

		addPlugin: function() {
			var self = this;
			$(arguments).each(function() {
				self.plugins.push(this);
			});
		},

		acceptsType: function(type) {
			var whitelisted = (this.options.accept.length == 0) || (this.options.accept.length > 0 && this.options.accept.indexOf(type) > -1);
			var blacklisted = this.options.ignore.length > 0 && this.options.ignore.indexOf(type) > -1;
			return whitelisted && !blacklisted;
		},

		call: function(type, arg) {
			if (!this.options.enable || !this.acceptsType(type)) {
				return false;
			}

			arg = arg || null;

			// call events
			$(this).trigger(type, [type, arg])
			$(this).trigger('default', [type, arg]);

			// call methods
			this.__call_method(type, type, arg);
			if (type != 'default') {
				this.__call_method('default', type, arg);
			}

			// call plugins
			$.each(this.plugins, function(i, plugin) {
				plugin.call(type, arg);
			});

			return true;
		},

		__call_method: function(method, type, arg) {
			var method = '_' + method;
			if (typeof(this[method]) == 'function') {
				this[method].call(this, type, arg);
				return true;
			}
			return false;
		}

	}

	Dopamine.extend = function(prototype) {
		var newDopamine = function() {
			this.init.apply(this, arguments);
		}
		newDopamine.prototype = $.extend({}, Dopamine.prototype, prototype);
		return newDopamine;
	}

	// globalize class
	window.Dopamine = Dopamine;

})(window.jQuery);
<commit_msg>add interface for $.Callbacks<commit_after>/*!
 * dopamine.js
 *
 * Copyright 2011, Armin Dressler
 * Licensed under the MIT license.
 * See LICENSE for details.
 */
;(function($) {

	var default_options = {
		enable: true,
		accept: [],
		ignore: []
	}

	var Dopamine = function() {
		this.init.apply(this, arguments);
	}

	Dopamine.prototype = {

		name: 'Dopamine',

		init: function(options) {
			options = options || {};

			this.plugins = [];
			this.callbacks = {};
			this.options = $.extend({}, default_options, options);
		},

		bind: function(type, func) {
			$(this).bind(type, func);
		},

		unbind: function(type, func) {
			$(this).unbind(type, func);
		},

		addPlugin: function() {
			var self = this;
			$(arguments).each(function() {
				self.plugins.push(this);
			});
		},

		addCallback: function(type, arg) {
			var map = {};
			if (typeof(type) == 'object') {
				map = type;
			} else {
				map[type] = arg;
			}

			var self = this;
			$.each(map, function(type, func) {
				self.callbacks[type] = self.callbacks[type] || $.Callbacks();
				self.callbacks[type].add(func);
			});
		},

		acceptsType: function(type) {
			var whitelisted = (this.options.accept.length == 0) || (this.options.accept.length > 0 && this.options.accept.indexOf(type) > -1);
			var blacklisted = this.options.ignore.length > 0 && this.options.ignore.indexOf(type) > -1;
			return whitelisted && !blacklisted;
		},

		call: function(type, arg) {
			if (!this.options.enable || !this.acceptsType(type)) {
				return false;
			}

			arg = arg || null;

			// call events, callbacks and method
			this.__call_event(type, type, arg);
			this.__call_callback(type, type, arg);
			this.__call_method(type, type, arg);

			// call 'default' events, callbacks and method
			if (type != 'default') {
				this.__call_event('default', type, arg);
				this.__call_callback('default', type, arg);
				this.__call_method('default', type, arg);
			}

			// call plugins
			this.__call_plugins(type, type, arg);

			return true;
		},

		__call_event: function(method, type, arg) {
			$(this).trigger(method, [type, arg])
		},

		__call_method: function(method, type, arg) {
			var method = '_' + method;
			if (typeof(this[method]) == 'function') {
				this[method].call(this, type, arg);
				return true;
			}
			return false;
		},

		__call_callback: function(method, type, arg) {
			if (typeof(this.callbacks[method]) == 'object') {
				this.callbacks[method].fire(type, arg);
				return true;
			}
			return false;
		},

		__call_plugins: function(type, arg) {
			$.each(this.plugins, function(i, plugin) {
				plugin.call(type, arg);
			});
		}
	}

	Dopamine.extend = function(prototype) {
		var newDopamine = function() {
			this.init.apply(this, arguments);
		}
		newDopamine.prototype = $.extend({}, Dopamine.prototype, prototype);
		return newDopamine;
	}

	// globalize class
	window.Dopamine = Dopamine;

})(window.jQuery);
<|endoftext|>


<commit_before>var extendedChart;
var extendedChartLegend;

$(document).ready(function() {
	$('div.btn-group[data-toggle-name=is_private]').each(function(){
		var group   = $(this);
		var form    = group.parents('form').eq(0);
		var name    = group.attr('data-toggle-name');
		var hidden  = $('input[name="' + name + '"]', form);
		$('button', group).each(function(){
			var button = $(this);
			button.live('click', function(){
				hidden.val($(this).text());
				setExtendedChartRenderer($(this).text());
			});
			if(button.val() == hidden.val()) {
				button.addClass('active');
			}
		});
	});
});

Rickshaw.Fixtures.Number.formatBase1024KMGTPShort = function(y) {
    abs_y = Math.abs(y);
    if (abs_y >= 1125899906842624)  { return parseFloat(y / 1125899906842624).toFixed(2) + "P" }
    else if (abs_y >= 1099511627776){ return parseFloat(y / 1099511627776).toFixed(2) + "T" }
    else if (abs_y >= 1073741824)   { return parseFloat(y / 1073741824).toFixed(2) + "G" }
    else if (abs_y >= 1048576)      { return parseFloat(y / 1048576).toFixed(2) + "M" }
    else if (abs_y >= 1024)         { return parseFloat(y / 1024).toFixed(2) + "K" }
    else if (abs_y < 1 && y > 0)    { return parseFloat(y).toFixed(2) }
    else if (abs_y === 0)           { return '' }
    else                        	{ return y.toFixed(2) }
};

var palette = new Rickshaw.Color.Palette({
	scheme: 'colorwheel'
});

function loadExtendedGraph(target){
	showExtendedGrapProgresshMessge("Accessing Graphite metrics API");
	$('#extendedChart').empty();
	$('#extendedLegend').empty();
	$('#timeline').empty();
	$.ajax({
		type: "get",
		url: target + "&format=json&jsonp=?",
		dataType:'json',
		success: function(json) {
			showExtendedGrapProgresshMessge("Rendering metrics to graph");
			var data = transformGraphiteData(json);
			//console.profile("render extended graph");
			renderExtendedGraph(target, data);
			//console.profileEnd();
			showExtendedGrapProgresshMessge("");
		},
		error:function (xhr, ajaxOptions, thrownError){
			console.log(thrownError);
		}
	});
}

function loadExtendedEvents(annotator){
	showExtendedGrapProgresshMessge("Accessing events API");
	$.ajax({
		type: "get",
		url: graphitusConfig.eventsUrl,
		dataType:'json',
		success: function(json) {
			showExtendedGrapProgresshMessge("Processing events");
			$.each(json, function(i, event) {
				var start = moment(event.start, "HH:mm:ss DD/MM/YYYY");
				var end = moment(event.end, "HH:mm:ss DD/MM/YYYY");
				//console.log(event.message+":"+ moment(event.timestamp, "HH:mm:ss DD/MM/YYYY").toString());
				var message = "" + event.message + "";
				var annotationContent = "["+start.format("HH:mm") +((end) ? "-" + end.format("HH:mm") : "") + "] - "+ message;
				annotator.add(start.add('hours', 3).unix(), annotationContent, (end) ?  end.add('hours', 3).unix() : null);
			});
			extendedChart.update();
			showExtendedGrapProgresshMessge("");
		},
		error:function (xhr, ajaxOptions, thrownError){
			console.log(thrownError);
		}
	});
}

function calculateTimeZoneOffset(){
	return 14400;
}

function renderExtendedGraph(target, data){
	var renderer =  (target.indexOf('stacked') == -1) ? 'line' : 'area';
	$('#'+renderer).click();
	extendedChart = new Rickshaw.Graph({
		element: document.getElementById("extendedChart"),
		width: $(window).width()-370,
		height: $(window).height()-250,
		renderer: renderer,
		stroke: true,
		min: "auto",
		interpolation: 'cardinal',
		series: data
	});

	extendedChart.render();

	var slider = new Rickshaw.Graph.RangeSlider( {
		graph: extendedChart,
		element: $('#slider')
	} );

	var hoverDetail = new Rickshaw.Graph.HoverDetail( {
	    graph: extendedChart,
		formatter: function(series, x, y, formattedX, formattedY, d) {
			return "" + series.name + " -  " + Rickshaw.Fixtures.Number.formatBase1024KMGTPShort(y) +"";
		},
	    xFormatter: function(x) {
	        return moment(((x-7200)*1000), "").format("YYYY-MM-DD HH:mm")
	    }
	} );

	var annotator = new Rickshaw.Graph.Annotate( {
		graph: extendedChart,
		element: document.getElementById('timeline')
	} );

	loadExtendedEvents(annotator);

	extendedChartLegend = new Rickshaw.Graph.Legend( {
		graph: extendedChart,
		element: document.getElementById('extendedLegend')
	} );
	var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
		graph: extendedChart,
		legend: extendedChartLegend
	} );
	var order = new Rickshaw.Graph.Behavior.Series.Order( {
		graph: extendedChart,
		legend: extendedChartLegend
	} );

	var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight( {
		graph: extendedChart,
		legend: extendedChartLegend
	} );

	var smoother = new Rickshaw.Graph.Smoother( {
		graph: extendedChart,
		element: $('#smoother')
	} );

	var numDataPoints = data[0].data.length;
	var largeDataScaling = Math.round(numDataPoints/1000)*2;
	if(largeDataScaling > 1){
		console.log("scaling rickshaw graph down by a smoothing factor of: " + largeDataScaling +
				"in order not to crash the browser because a large number of datapoints was: " + numDataPoints);

		showExtendedGrapProgresshWarning("whoa! lots of data points, smoothing factor: " + largeDataScaling);
		smoother.setScale(largeDataScaling);
	}

	var xAxis = new Rickshaw.Graph.Axis.Time( {
		graph: extendedChart,
		ticksTreatment: 'glow'
	} );

	var smoother = new Rickshaw.Graph.Smoother( {
		graph: extendedChart,
		element: $('#smoother'),
	} );

	xAxis.render();

	var yAxis = new Rickshaw.Graph.Axis.Y( {
		graph: extendedChart,
		ticksTreatment: 'glow',
		tickFormat: Rickshaw.Fixtures.Number.formatBase1024KMGTPShort
	} );

	yAxis.render();

	var controls = new RenderControls( {
		element: document.querySelector('#extendedChartOptionsForm'),
		graph: extendedChart
	} );

	$('#extendedLegend').height($('#extendedChart').height());

	$('#lightboxProgress').hide();
}

function transformGraphiteData(data) {
    resultData = _.map(data, function(item) {
      return {
        "color": palette.color(),
        "name": item.target.replace(/_/g, ' '),
        "data": graphiteToRickshawModel(item.datapoints)
      };
    });
    return resultData;
}

function graphiteToRickshawModel(datapoints){
	var lastX = 0;
	var ret = _.map(datapoints, function(point) {
		lastX = point[0] !== null ? point[0] : lastX
		return {
		  'x': point[1]-calculateTimeZoneOffset(),
		  'y':  point[0]
		};
	});
	return ret;
}

function setExtendedChartRenderer(renderer){
	extendedChart.renderer = renderer;
	extendedChart.configure(extendedChart);
	extendedChart.render();
}

function legendSelectAll(){
	for ( var i = 0; i < extendedChartLegend.lines.length; i++ ) {
		var line = extendedChartLegend.lines[i];
		line.element.classList.remove('disabled');
		line.series.enable();
	}
}
function legendSelectNone(){
	for ( var i = 1; i < extendedChartLegend.lines.length; i++ ) {
        var line = extendedChartLegend.lines[i];
        line.element.classList.add('disabled');
		line.series.disable();
	}
}

function showExtendedGrapProgresshMessge(msg){
	$("#lightboxProgressText").text(msg);
	if(msg == ""){
		$("#lightboxProgress").hide();
	}else{
		$("#lightboxProgress").show();
	}
}

function showExtendedGrapProgresshWarning(msg){
	$("#lightboxProgressWarningWarning").text(msg);
	$("#lightboxProgressWarningWarning").show();
}
	<commit_msg>added missing file from previous commit<commit_after>var extendedChart;
var extendedChartLegend;

$(document).ready(function() {
	$('div.btn-group[data-toggle-name=is_private]').each(function(){
		var group   = $(this);
		var form    = group.parents('form').eq(0);
		var name    = group.attr('data-toggle-name');
		var hidden  = $('input[name="' + name + '"]', form);
		$('button', group).each(function(){
			var button = $(this);
			button.live('click', function(){
				hidden.val($(this).text());
				setExtendedChartRenderer($(this).text());
			});
			if(button.val() == hidden.val()) {
				button.addClass('active');
			}
		});
	});
});

Rickshaw.Fixtures.Number.formatBase1024KMGTPShort = function(y) {
    return formatBase1024KMGTPShort(y);
};

var palette = new Rickshaw.Color.Palette({
	scheme: 'colorwheel'
});

function loadExtendedGraph(target, dashboardTitle, graphTitle){
	$("#extendedGraphTitle").text( dashboardTitle+ " - " + graphTitle);
	showExtendedGrapProgresshMessge("Accessing Graphite metrics API");
	$('#extendedChart').empty();
	$('#extendedLegend').empty();
	$('#timeline').empty();
	loadGraphiteData(target, function(json){
		showExtendedGrapProgresshMessge("Rendering metrics to graph");
		var data = transformGraphiteData(json);
		//console.profile("render extended graph");
		renderExtendedGraph(target, data);
		//console.profileEnd();
		showExtendedGrapProgresshMessge("");
	});
}

function loadExtendedEvents(annotator){
	showExtendedGrapProgresshMessge("Accessing events API");
	$.ajax({
		type: "get",
		url: graphitusConfig.eventsUrl,
		dataType:'json',
		success: function(json) {
			showExtendedGrapProgresshMessge("Processing events");
			$.each(json, function(i, event) {
				var start = moment(event.start, "HH:mm:ss DD/MM/YYYY");
				var end = moment(event.end, "HH:mm:ss DD/MM/YYYY");
				//console.log(event.message+":"+ moment(event.timestamp, "HH:mm:ss DD/MM/YYYY").toString());
				var message = "" + event.message + "";
				var annotationContent = "["+start.format("HH:mm") +((end) ? "-" + end.format("HH:mm") : "") + "] - "+ message;
				annotator.add(start.add('hours', 3).unix(), annotationContent, (end) ?  end.add('hours', 3).unix() : null);
			});
			extendedChart.update();
			showExtendedGrapProgresshMessge("");
		},
		error:function (xhr, ajaxOptions, thrownError){
			console.log(thrownError);
		}
	});
}

function calculateTimeZoneOffset(){
	return 14400;
}

function renderExtendedGraph(target, data){
	var renderer =  (target.indexOf('stacked') == -1) ? 'line' : 'area';
	$('#'+renderer).click();
	extendedChart = new Rickshaw.Graph({
		element: document.getElementById("extendedChart"),
		width: $(window).width()-370,
		height: $(window).height()-250,
		renderer: renderer,
		stroke: true,
		min: "auto",
		interpolation: 'cardinal',
		series: data
	});

	extendedChart.render();

	var slider = new Rickshaw.Graph.RangeSlider( {
		graph: extendedChart,
		element: $('#slider')
	} );

	var hoverDetail = new Rickshaw.Graph.HoverDetail( {
	    graph: extendedChart,
		formatter: function(series, x, y, formattedX, formattedY, d) {
			return "" + series.name + " -  " + Rickshaw.Fixtures.Number.formatBase1024KMGTPShort(y) +"";
		},
	    xFormatter: function(x) {
	        return moment(((x-7200)*1000), "").format("YYYY-MM-DD HH:mm")
	    }
	} );

	var annotator = new Rickshaw.Graph.Annotate( {
		graph: extendedChart,
		element: document.getElementById('timeline')
	} );

	loadExtendedEvents(annotator);

	extendedChartLegend = new Rickshaw.Graph.Legend( {
		graph: extendedChart,
		element: document.getElementById('extendedLegend')
	} );
	var shelving = new Rickshaw.Graph.Behavior.Series.Toggle( {
		graph: extendedChart,
		legend: extendedChartLegend
	} );
	var order = new Rickshaw.Graph.Behavior.Series.Order( {
		graph: extendedChart,
		legend: extendedChartLegend
	} );

	var highlighter = new Rickshaw.Graph.Behavior.Series.Highlight( {
		graph: extendedChart,
		legend: extendedChartLegend
	} );

	var smoother = new Rickshaw.Graph.Smoother( {
		graph: extendedChart,
		element: $('#smoother')
	} );

	var numDataPoints = data[0].data.length;
	var largeDataScaling = Math.round(numDataPoints/1000)*2;
	if(largeDataScaling > 1){
		console.log("scaling rickshaw graph down by a smoothing factor of: " + largeDataScaling +
				"in order not to crash the browser because a large number of datapoints was: " + numDataPoints);

		showExtendedGrapProgresshWarning("whoa! lots of data points, smoothing factor: " + largeDataScaling);
		smoother.setScale(largeDataScaling);
	}

	var xAxis = new Rickshaw.Graph.Axis.Time( {
		graph: extendedChart,
		ticksTreatment: 'glow'
	} );

	var smoother = new Rickshaw.Graph.Smoother( {
		graph: extendedChart,
		element: $('#smoother'),
	} );

	xAxis.render();

	var yAxis = new Rickshaw.Graph.Axis.Y( {
		graph: extendedChart,
		ticksTreatment: 'glow',
		tickFormat: Rickshaw.Fixtures.Number.formatBase1024KMGTPShort
	} );

	yAxis.render();

	var controls = new RenderControls( {
		element: document.querySelector('#extendedChartOptionsForm'),
		graph: extendedChart
	} );

	$('#extendedLegend').height($('#extendedChart').height());

	$('#lightboxProgress').hide();
}

function transformGraphiteData(data) {
    resultData = _.map(data, function(item) {
      return {
        "color": palette.color(),
        "name": item.target.replace(/_/g, ' '),
        "data": graphiteToRickshawModel(item.datapoints)
      };
    });
    return resultData;
}

function graphiteToRickshawModel(datapoints){
	var lastX = 0;
	var ret = _.map(datapoints, function(point) {
		lastX = point[0] !== null ? point[0] : lastX
		return {
		  'x': point[1]-calculateTimeZoneOffset(),
		  'y':  point[0]
		};
	});
	return ret;
}

function setExtendedChartRenderer(renderer){
	extendedChart.renderer = renderer;
	extendedChart.configure(extendedChart);
	extendedChart.render();
}

function legendSelectAll(){
	for ( var i = 0; i < extendedChartLegend.lines.length; i++ ) {
		var line = extendedChartLegend.lines[i];
		line.element.classList.remove('disabled');
		line.series.enable();
	}
}
function legendSelectNone(){
	for ( var i = 1; i < extendedChartLegend.lines.length; i++ ) {
        var line = extendedChartLegend.lines[i];
        line.element.classList.add('disabled');
		line.series.disable();
	}
}

function showExtendedGrapProgresshMessge(msg){
	$("#lightboxProgressText").text(msg);
	if(msg == ""){
		$("#lightboxProgress").hide();
	}else{
		$("#lightboxProgress").show();
	}
}

function showExtendedGrapProgresshWarning(msg){
	$("#lightboxProgressWarningWarning").text(msg);
	$("#lightboxProgressWarningWarning").show();
}
<|endoftext|>


<commit_before>const geometry = require('../geometry');
const controlTheory = require('../controlTheory');

const FollowWall = function (controllers) {

  var accumulatedError = 0;
  var previousError = 0;
  var previousSlide = null;

  const followWallDistance = 0.1;

  const getSensorGroup = function (sensors, sensorGroups) {
    // Get all sensors belonging to required group
    const group = sensors.filter(function (sensor) {
      return ~sensorGroups.indexOf(sensor.group);
    });

    // Sort them with smallest reading first and largest importance first
    return group.sort(function (s1, s2) {
      return s1.distance - s2.distance;
    });
  };

  const sumSensorReadings = function (sensors) {
    return sensors.reduce(function (total, sensor) {
      return total + sensor.distance;
    }, 0);
  };

  const getWallSensors = function (sensors) {
    // Group the sensors so we can find out which side the wall is on
    const leftGroup = getSensorGroup(sensors, [controllers.sensorGroups.Front, controllers.sensorGroups.Left]);
    const rightGroup = getSensorGroup(sensors, [controllers.sensorGroups.Front, controllers.sensorGroups.Right]);
    const leftReading = sumSensorReadings(leftGroup);
    const rightReading = sumSensorReadings(rightGroup);

    const getResult = function (slidingMode, leftGroup, rightGroup) {
      const getSensorsInDirectionOfTravel = function (sensors) {
        const twoClosestSensors = sensors.slice(0, 2);
        return ['FF', 'FR', 'FL', 'BR', 'BL'].reduce(function (ordered, id) {
          twoClosestSensors.forEach(function (sensor) {
            if (id === sensor.id) {
              ordered.push(sensor);
            }
          });
          return ordered;
        }, []);
      };

      return {
        sliding: slidingMode,
        sensors: getSensorsInDirectionOfTravel(slidingMode === controllers.sensorGroups.Right ? rightGroup : leftGroup)
      };
    };

    // Return 2 sensors that are closest to the wall on the correct side
    if (leftReading > rightReading) {
      return getResult(controllers.sensorGroups.Right, leftGroup, rightGroup);
    } else if (leftReading === rightReading) {
      // If readings are the same assume that sensors are not reading anything and
      // start to turn into the wall, assuming that the wall has ended. Turn into the wall by switching
      // sensor groups around
      // TODO this can turn you into the obstacle, maybe a better way to do this?
      return getResult(previousSlide, rightGroup, leftGroup);
    } else {
      return getResult(controllers.sensorGroups.Left, leftGroup, rightGroup);
    }
  };

  const getSensorReadingPoint = function (state, sensor) {
    const sensorVector = sensor.getVector(state, sensor.distance);
    return sensorVector.end;
  };

  const setProgressMade = function (state) {
    if (!state.progressMade) {
      state.progressMade = geometry.norm(geometry.createPoint(state.goal.x - state.x, state.goal.y, state.y));
    }
  };

  const execute = function (state) {
    setProgressMade(state);

    const sensorResult = getWallSensors(state.sensors);
    previousSlide = sensorResult.sliding;

    const p1 = getSensorReadingPoint(state, sensorResult.sensors[0]);
    const p2 = getSensorReadingPoint(state, sensorResult.sensors[1]);

    // Calculate the wall segment in the direction of travel
    const uFwT = geometry.createPoint(p1.x - p2.x, p1.y - p2.y);

    // Calculate the vector from the robot to the wall segment
    const uFwTNorm = geometry.norm(uFwT);
    const uFwTP = geometry.createPoint(uFwT.x / uFwTNorm, uFwT.y / uFwTNorm);
    const uA = p2;
    const uP = geometry.createPoint(state.x, state.y);

    // Calculate: u_fw_p = ((u_a-u_p)-((u_a-u_p)'*u_fw_tp)*u_fw_tp)
    const uAuPDiff = geometry.createPoint(uA.x - uP.x, uA.y - uP.y);
    const uAuPDiffDotUFwTP = uAuPDiff.x * uFwTP.x + uAuPDiff.y * uFwTP.y;

    const uFwP = geometry.createPoint(uAuPDiff.x - uAuPDiffDotUFwTP * uFwTP.x, uAuPDiff.y - uAuPDiffDotUFwTP * uFwTP.y);

    // Get the vector perpendicular to the wall segment that will keep you a constant
    // distance from the segment
    const uFwPNorm = geometry.norm(uFwP);
    const uFwPP = geometry.createPoint(uFwP.x / uFwPNorm, uFwP.y / uFwPNorm);
    const uFw = geometry.createPoint(
      followWallDistance * uFwTP.x + (uFwP.x - followWallDistance * uFwPP.x),
      followWallDistance * uFwTP.y + (uFwP.y - followWallDistance * uFwPP.y)
    );

    // Calculate heading
    state.followWall = geometry.createLine(geometry.createPoint(0,0), uFw);
    state.wallSegment = geometry.createLine(p2, p1);
    state.followWallPerpendicular = geometry.createLine(geometry.createPoint(0,0), uFwP);

    const result = controlTheory.calculateTrajectory(state, uFw, accumulatedError, previousError);
    const w = result.w;
    previousError = result.thetaError;
    accumulatedError = result.accumulatedError;

    return {
      w: w,
      v: state.v
    };
  };

  const reset = function () {
    previousError = 0;
    accumulatedError = 0;
    previousSlide = null;
  };

  return {
    behaviour: controllers.behaviourTypes.FollowWall,
    execute: execute,
    reset: reset
  }
};

module.exports = FollowWall;<commit_msg>Initial attempt at going round wall corners better<commit_after>const geometry = require('../geometry');
const controlTheory = require('../controlTheory');

const FollowWall = function (controllers) {

  var accumulatedError = 0;
  var previousError = 0;
  var previousSlide = null;

  const followWallDistance = 0.1;

  const getSensorGroup = function (sensors, sensorGroups) {
    // Get all sensors belonging to required group
    const group = sensors.filter(function (sensor) {
      return ~sensorGroups.indexOf(sensor.group);
    });

    // Sort them with smallest reading first and largest importance first
    return group.sort(function (s1, s2) {
      return s1.distance - s2.distance;
    });
  };

  const sumSensorReadings = function (sensors) {
    return sensors.reduce(function (total, sensor) {
      return total + sensor.distance;
    }, 0);
  };

  const getWallSensors = function (sensors) {
    // Group the sensors so we can find out which side the wall is on
    const leftGroup = getSensorGroup(sensors, [controllers.sensorGroups.Front, controllers.sensorGroups.Left]);
    const rightGroup = getSensorGroup(sensors, [controllers.sensorGroups.Front, controllers.sensorGroups.Right]);
    const leftReading = sumSensorReadings(leftGroup);
    const rightReading = sumSensorReadings(rightGroup);

    const getResult = function (slidingMode, leftGroup, rightGroup) {
      const getSensorsInDirectionOfTravel = function (sensors) {
        const twoClosestSensors = sensors.slice(0, 2);

        // Ensure sensors are ordered correctly for direction of travel
        return ['FF', 'FR', 'FL', 'BR', 'BL'].reduce(function (ordered, id) {
          twoClosestSensors.forEach(function (sensor) {
            if (id === sensor.id) {
              ordered.push(sensor);
            }
          });
          return ordered;
        }, []);
      };

      return {
        sliding: slidingMode,
        lostWall: false,
        sensors: getSensorsInDirectionOfTravel(slidingMode === controllers.sensorGroups.Right ? rightGroup : leftGroup)
      };
    };

    // Return 2 sensors that are closest to the wall on the correct side
    if (leftReading > rightReading) {
      return getResult(controllers.sensorGroups.Right, leftGroup, rightGroup);
    } else if (leftReading === rightReading) {
      // If readings are the same assume that sensors are not reading anything and
      // start to turn into the wall, assuming that the wall has ended. Turn into the wall by switching
      // sensor groups around
      // TODO this can turn you into the obstacle, maybe a better way to do this?
      //return getResult(previousSlide, rightGroup, leftGroup);
      return {
        sliding: previousSlide,
        lostWall: true
      };
    } else {
      return getResult(controllers.sensorGroups.Left, leftGroup, rightGroup);
    }
  };

  const getSensorReadingPoint = function (state, sensor) {
    const sensorVector = sensor.getVector(state, sensor.distance);
    return sensorVector.end;
  };

  const setProgressMade = function (state) {
    if (!state.progressMade) {
      state.progressMade = geometry.norm(geometry.createPoint(state.goal.x - state.x, state.goal.y, state.y));
    }
  };

  const execute = function (state) {
    setProgressMade(state);

    const sensorResult = getWallSensors(state.sensors);
    previousSlide = sensorResult.sliding;

    if (sensorResult.lostWall) {
      // TODO attempt to just move towards wall gracefully
      const thetaError = sensorResult.sliding === controllers.sensorGroups.Right ? -Math.PI / 8 : Math.PI / 8;
      accumulatedError += thetaError * state.dt;

      const w = controlTheory.pid(thetaError, accumulatedError, (thetaError - previousError) / state.dt);
      previousError = thetaError;

      return {
        w: w,
        v: state.v
      };
    } else {

      const p1 = getSensorReadingPoint(state, sensorResult.sensors[0]);
      const p2 = getSensorReadingPoint(state, sensorResult.sensors[1]);

      // Calculate the wall segment in the direction of travel
      const uFwT = geometry.createPoint(p1.x - p2.x, p1.y - p2.y);

      // Calculate the vector from the robot to the wall segment
      const uFwTNorm = geometry.norm(uFwT);
      const uFwTP = geometry.createPoint(uFwT.x / uFwTNorm, uFwT.y / uFwTNorm);
      const uA = p2;
      const uP = geometry.createPoint(state.x, state.y);

      // Calculate: u_fw_p = ((u_a-u_p)-((u_a-u_p)'*u_fw_tp)*u_fw_tp)
      const uAuPDiff = geometry.createPoint(uA.x - uP.x, uA.y - uP.y);
      const uAuPDiffDotUFwTP = uAuPDiff.x * uFwTP.x + uAuPDiff.y * uFwTP.y;

      const uFwP = geometry.createPoint(uAuPDiff.x - uAuPDiffDotUFwTP * uFwTP.x, uAuPDiff.y - uAuPDiffDotUFwTP * uFwTP.y);

      // Get the vector perpendicular to the wall segment that will keep you a constant
      // distance from the segment
      const uFwPNorm = geometry.norm(uFwP);
      const uFwPP = geometry.createPoint(uFwP.x / uFwPNorm, uFwP.y / uFwPNorm);
      const uFw = geometry.createPoint(
        followWallDistance * uFwTP.x + (uFwP.x - followWallDistance * uFwPP.x),
        followWallDistance * uFwTP.y + (uFwP.y - followWallDistance * uFwPP.y)
      );

      // Calculate heading
      state.followWall = geometry.createLine(geometry.createPoint(0, 0), uFw);
      state.wallSegment = geometry.createLine(p2, p1);
      state.followWallPerpendicular = geometry.createLine(geometry.createPoint(0, 0), uFwP);

      const result = controlTheory.calculateTrajectory(state, uFw, accumulatedError, previousError);
      previousError = result.thetaError;
      accumulatedError = result.accumulatedError;

      return {
        w: result.w,
        v: state.v
      };
    }
  };

  const reset = function () {
    previousError = 0;
    accumulatedError = 0;
    previousSlide = null;
  };

  return {
    behaviour: controllers.behaviourTypes.FollowWall,
    execute: execute,
    reset: reset
  }
};

module.exports = FollowWall;<|endoftext|>