<commit_before>define('BandController', [
    'audioConfig',
    'bandConfig',
    'bandService'
], function(audioConfig, bandConfig, bandService) {
    'use strict';

    var $bandContainer =
        document.querySelector('#animalBand .band__container'),
        renderer,
        stage,
        numberOfClicksOnMonkey = 0,
        text,
        animationObjects = {},
        blinkingIdle;

    return {
        init: init,
        setIdleAnimationState: setIdleAnimationState
    };

    function init() {
        setRenderer();
        setStage();
        toggleAnimalAnimation();
    }

    function setRenderer() {
        renderer = PIXI.autoDetectRenderer(
            $bandContainer.offsetWidth,
            $bandContainer.offsetHeight,
            bandConfig.rendererOptions
        );
        bandService.setRendererStyle(renderer);
        renderer.autoResize = true;
        $bandContainer.appendChild(renderer.view);
    }

    function setStage() {
        stage = new PIXI.Container();
        PIXI.ticker.shared.add(renderHandler);
        setAnimalAnimation();

        function renderHandler (deltaTime) {
            renderer.render(stage);
        };
    }

    function setAnimalAnimation() {
        var factory = new dragonBones.PixiFactory(),
            animationData;

        for(var animation in bandConfig.animations) {
            animationData = bandConfig.animations[animation].data
            PIXI.loader.add(animation + 'Bones', animationData.bones)
            PIXI.loader.add(animation + 'Texture', animationData.texture)
            PIXI.loader.add(animation + 'Atlas', animationData.atlas)
        }

        PIXI.loader.once("complete", onLoadComplete);
        PIXI.loader.load();

        function onLoadComplete (loader, object) {
            for(var animation in bandConfig.animations) {
                factory.parseDragonBonesData(
                    object[animation + 'Bones'].data
                );
                factory.parseTextureAtlasData(
                    object[animation + 'Texture'].data,
                    object[animation + 'Atlas'].texture
                );
                animationObjects[animation] = factory.buildArmatureDisplay(
                    animation.charAt(0).toUpperCase() + animation.slice(1)
                );
                animationObjects[animation].scale.set(
                    bandConfig.animations[animation].scale
                );
                animationObjects[animation].x =
                    renderer.width * bandConfig.animations[animation].position.x;
                animationObjects[animation].y =
                    renderer.height * bandConfig.animations[animation].position.y;
                animationObjects[animation].animation.timeScale = 1;
                animationObjects[animation].animation.gotoAndPlay('Idle', 0.3, -1, 0);
            }
            setBackground();
            setIdleAnimationState();
        }

        function setBackground() {
            var backgroud = new PIXI.Sprite(
                PIXI.Texture.fromImage(bandConfig.backgroundImg
            ));
            backgroud.width = renderer.width;
            backgroud.height = renderer.height;
            stage.addChild(backgroud);
        }

        function setAnimationItemsonStage() {
            for(var animation in animations) {
                stage.addChild(animationObjects[animation]);
            }
        }
    }

    function setIdleAnimationState() {
        for(var animation in bandConfig.animations) {
            animationObjects[animation].animation.timeScale = 1;
            animationObjects[animation].animation.gotoAndPlay('Idle', 0.3, -1, 0);
            stage.addChild(animationObjects[animation]);
        }
    }

    function toggleAnimalAnimation() {
        document.querySelector('.timeline')
            .addEventListener('animalBand.audio.rhythmIndex', function(e) {

                audioConfig.matrix.result.map(function(instrument, index) {
                    var rhythmIndex = e.detail.rhythmIndex,
                        animation = bandConfig.animations[instrument],
                        isSet = audioConfig.matrix.entities[instrument][rhythmIndex],
                        tom1 = audioConfig.matrix.entities['tom1'][rhythmIndex],
                        tom2 = audioConfig.matrix.entities['tom2'][rhythmIndex],
                        isSetTom1Tom2 = tom1 && tom2
                    ;

                    if(isSet) {
                        if(isSetTom1Tom2){
                            if(animation.id == 'lion'){
                                //animation play speed
                                animationObjects[animation.id].animation.timeScale = 2;
                                animationObjects['lion'].animation.gotoAndPlay(
                                bandConfig.animations['tom1_tom2'].types[Math.floor(Math.random()*animation.types.length)], -1, -1, 1);
                             }
                            else {
                                animationObjects[animation.id].animation.timeScale = 2;
                                animationObjects[animation.id].animation.gotoAndPlay(animation.types[Math.floor(Math.random()*animation.types.length)],-1,-1,1);
                            }

                        }
                        else{
                            animationObjects[animation.id].animation.timeScale = 2;
                            animationObjects[animation.id].animation.gotoAndPlay(animation.types[Math.floor(Math.random()*animation.types.length)],-1,-1,1);
                        }
                    } else {
                        //if tom1 is playing dont animate Idle for tom2
                        if(!tom1){
                            animationObjects[animation.id].animation.timeScale = 2;

                            //blinking if idle
                            var animation = bandConfig.animations[instrument]
                            var rand = Math.floor((Math.random() * 100) + 1);
                            if( rand < 10) {
                                animationObjects[animation.id].animation.gotoAndPlay('Idle_Blink', -1, -1, 1);
                            }
                            else
                            {
                                 animationObjects[animation.id].animation.gotoAndPlay('Idle', -1, -1, 1);
                            }

                        }
                    }
                });
            });
    }
});
<commit_msg>Optimize animation music handler<commit_after>define('BandController', [
    'audioConfig',
    'bandConfig',
    'bandService',
    'arrayHelpers'
], function(audioConfig, bandConfig, bandService, arrayHelpers) {
    'use strict';

    var $bandContainer =
        document.querySelector('#animalBand .band__container'),
        renderer,
        stage,
        numberOfClicksOnMonkey = 0,
        text,
        animationObjects = {},
        blinkingIdle;

    return {
        init: init,
        setIdleAnimationState: setIdleAnimationState
    };

    function init() {
        setRenderer();
        setStage();
    }

    function setRenderer() {
        renderer = PIXI.autoDetectRenderer(
            $bandContainer.offsetWidth,
            $bandContainer.offsetHeight,
            bandConfig.rendererOptions
        );
        bandService.setRendererStyle(renderer);
        renderer.autoResize = true;
        $bandContainer.appendChild(renderer.view);
    }

    function setStage() {
        stage = new PIXI.Container();
        PIXI.ticker.shared.add(renderHandler);
        initAnimalAnimation();

        function renderHandler (deltaTime) {
            renderer.render(stage);
        };
    }

    function initAnimalAnimation() {
        var factory = new dragonBones.PixiFactory(),
            animationData;

        for(var animation in bandConfig.animations) {
            animationData = bandConfig.animations[animation].data
            PIXI.loader.add(animation + 'Bones', animationData.bones)
            PIXI.loader.add(animation + 'Texture', animationData.texture)
            PIXI.loader.add(animation + 'Atlas', animationData.atlas)
        }

        PIXI.loader.once("complete", onLoadComplete);
        PIXI.loader.load();

        function onLoadComplete (loader, object) {
            for(var animation in bandConfig.animations) {
                factory.parseDragonBonesData(
                    object[animation + 'Bones'].data
                );
                factory.parseTextureAtlasData(
                    object[animation + 'Texture'].data,
                    object[animation + 'Atlas'].texture
                );
                animationObjects[animation] = factory.buildArmatureDisplay(
                    animation.charAt(0).toUpperCase() + animation.slice(1)
                );
                animationObjects[animation].scale.set(
                    bandConfig.animations[animation].scale
                );
                animationObjects[animation].x =
                    renderer.width * bandConfig.animations[animation].position.x;
                animationObjects[animation].y =
                    renderer.height * bandConfig.animations[animation].position.y;
                animationObjects[animation].animation.timeScale = 1;
                animationObjects[animation].animation.gotoAndPlay('Idle', 0.3, -1, 0);
            }
            setBackground();
            setMusicHandler();
            setIdleAnimationState();
        }

        function setBackground() {
            var backgroud = new PIXI.Sprite(
                PIXI.Texture.fromImage(bandConfig.backgroundImg
            ));
            backgroud.width = renderer.width;
            backgroud.height = renderer.height;
            stage.addChild(backgroud);
        }

        function setAnimationItemsonStage() {
            for(var animation in animations) {
                stage.addChild(animationObjects[animation]);
            }
        }
    }

    function setIdleAnimationState() {
        for(var animation in bandConfig.animations) {
            animationObjects[animation].animation.timeScale = 1;
            animationObjects[animation].animation.gotoAndPlay('Idle', 0.3, -1, 0);
            stage.addChild(animationObjects[animation]);
        }
    }

    function setMusicHandler() {
        document.querySelector('.timeline')
            .addEventListener('animalBand.audio.rhythmIndex', function(e) {
                audioConfig.matrix.result.map(function(instrument, index) {
                    var rhythmIndex = e.detail.rhythmIndex,
                        animationId = bandConfig.animationsMapping[instrument],
                        animationData = bandConfig.animations[animationId],
                        isInstrumentSet = audioConfig.matrix.entities[instrument][rhythmIndex],
                        isTom1Set = audioConfig.matrix.entities['tom1'][rhythmIndex],
                        isTom2Set = audioConfig.matrix.entities['tom2'][rhythmIndex],
                        animationType;

                    if(isInstrumentSet) {
                        if(animationId === 'lion') {
                            if(isTom1Set && isTom2Set) {
                                animationType = arrayHelpers.getRandomValue(
                                    animationData.types.tom1tom2
                                );
                            } else if (isTom1Set) {
                                animationType = arrayHelpers.getRandomValue(
                                    animationData.types.tom1
                                );
                            } else if (isTom2Set) {
                                animationType = arrayHelpers.getRandomValue(
                                    animationData.types.tom2
                                );
                            }
                        } else {
                            animationType = arrayHelpers.getRandomValue(
                                animationData.types
                            );
                        }
                        animationObjects[animationId].animation.timeScale = 2;
                        animationObjects[animationId].animation.gotoAndPlay(
                            animationType, -1, -1, 1
                        );
                    } else {
                        if(!isTom1Set){
                            animationObjects[animationId].animation.timeScale = 2;
                            var animation = bandConfig.animations[instrument]
                            var rand = Math.floor((Math.random() * 100) + 1);
                            if(rand < 10) {
                                animationObjects[animationId].animation.gotoAndPlay(
                                    'Idle_Blink', -1, -1, 1
                                );
                            }
                            else {
                                 animationObjects[animationId].animation.gotoAndPlay(
                                     'Idle', -1, -1, 1
                                 );
                            }
                        }
                    }
                });
            });
    }
});
<|endoftext|>


<commit_before>import * as d3 from 'd3';
import {crossfilter, units, geoChoroplethChart, bubbleChart, renderAll, redrawAll, filterAll, pieChart, barChart, dataCount, dataTable, pluck} from 'dc';

//we can call export at the top of the class declaration
export default class ThrashDashDC {

    constructor(el, props = {}) {
        //we initiate charts in constructor

        console.log('the construcor');
        this.numberFormat = d3.format(".2f");

    }

    render() {
        console.log('in render');
        d3.json('src/stores/thrashtown.json', function (error, data) {
            var surfData = data;

            const fullDateFormat = d3.time.format("%a, %d %b %Y %X %Z");
            const yearFormat = d3.time.format('%Y');
            const monthFormat = d3.time.format('%b');
            const dayFormat = d3.time.format('%a');

            surfData.forEach(d=>{
              /*d.sessionDateDt = new Date(d.sessionDate);
                 d.sessionYearDt = d.sessionDateDt.getUTCFullYear();
                 d.sessionMonthDt = d.sessionDateDt.getUTCMonth();
                 console.log(typeof(d.sessionMonthDt));*/

                let dateObj  = new Date(d.sessionDate);
                d.sessionDateFormatted = fullDateFormat(dateObj);
                d.sessionYear = +yearFormat(dateObj);
                d.sessionMonth = monthFormat(dateObj);
                console.log(typeof(d.hollowness));
                d.sessionDay = dayFormat(dateObj);

            });

            const ttx = crossfilter(surfData);

            // create dimensions (x-axis values)
            var hollowFactorDim  = ttx.dimension(pluck("hollowness"));
            var crowdFactorDim  = ttx.dimension(pluck("crowdedness"));
            var funFactorDim  = ttx.dimension(pluck("funFactor"));
            var yearDim  = ttx.dimension(pluck("sessionYear"));
            var monthDim  = ttx.dimension(pluck("sessionMonth"));
            var dayDim  = ttx.dimension(pluck("sessionDay"));

            // create groups (y-axis values)
            var all = ttx.groupAll();
            var countPerHollowFactor = hollowFactorDim.group().reduceCount();
            var countPerCrowdFactor = crowdFactorDim.group().reduceCount();
            var countPerFunFactor = funFactorDim.group().reduceCount();
            var countPerYear = yearDim.group().reduceCount();
            var countPerMonth = monthDim.group().reduceCount();
            var countPerDay = dayDim.group().reduceCount();

            //const hollowFactorChart = barChart('#chart-bar-hollow-factor');
            const crowdFactorChart = barChart('#chart-bar-crowd-factor');
            const funFactorChart = barChart('#chart-bar-fun-factor');
            const yearChart = pieChart('#chart-ring-year');
            const monthChart = pieChart('#chart-ring-month');
            const dayChart = pieChart('#chart-ring-day');

			crowdFactorChart
			  .width(300)
			  .height(180)
			  .dimension(crowdFactorDim)
			  .group(countPerCrowdFactor)
			  .x(d3.scale.linear().domain([0,5.2]))
			  .elasticY(true)
			  .centerBar(true)
			  .barPadding(5)
			  .xAxisLabel('Crowd Factor')
			  .yAxisLabel('Sessions')
			crowdFactorChart.xAxis().tickValues([0,1,2,3,4,5]);

			funFactorChart
			  .width(300)
			  .height(180)
			  .dimension(funFactorDim)
			  .group(countPerFunFactor)
			  .x(d3.scale.linear().domain([0,5.2]))
			  .elasticY(true)
			  .centerBar(true)
			  .barPadding(5)
			  .xAxisLabel('Fun Factor')
			  .yAxisLabel('Sessions')
			funFactorChart.xAxis().tickValues([0, 1, 2, 3, 4, 5]);

			yearChart
			  .width(150)
			  .height(150)
			  .dimension(yearDim)
			  .group(countPerYear)
			  .innerRadius(20);

			monthChart
			  .width(150)
			  .height(150)
			  .dimension(monthDim)
			  .group(countPerMonth)
			  .innerRadius(20)
			  .ordering(function (d) {
				var order = {
				  'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4,
				  'may': 5, 'jun': 6, 'jul': 7, 'aug': 8,
				  'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12
				};
				return order[d.key];
			  });

			dayChart
			  .width(150)
			  .height(150)
			  .dimension(dayDim)
			  .group(countPerDay)
			  .innerRadius(20)
			  .ordering(function (d) {
				var order = {
				  'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3,
				  'fri': 4, 'sat': 5, 'sun': 6
				}
				return order[d.key];
			  });

			//draw the viz!
			renderAll();

        });
    }
}
<commit_msg>add hollow chart<commit_after>import * as d3 from 'd3';
import {crossfilter, units, geoChoroplethChart, bubbleChart, renderAll, redrawAll, filterAll, pieChart, barChart, dataCount, dataTable, pluck} from 'dc';

//we can call export at the top of the class declaration
export default class ThrashDashDC {

    constructor(el, props = {}) {
        //we initiate charts in constructor

        console.log('the construcor');
        this.numberFormat = d3.format(".2f");

    }

    render() {
        console.log('in render');
        d3.json('src/stores/thrashtown.json', function (error, data) {
            var surfData = data;

            const fullDateFormat = d3.time.format("%a, %d %b %Y %X %Z");
            const yearFormat = d3.time.format('%Y');
            const monthFormat = d3.time.format('%b');
            const dayFormat = d3.time.format('%a');

            surfData.forEach(d=>{
              /*d.sessionDateDt = new Date(d.sessionDate);
                 d.sessionYearDt = d.sessionDateDt.getUTCFullYear();
                 d.sessionMonthDt = d.sessionDateDt.getUTCMonth();
                 console.log(typeof(d.sessionMonthDt));*/

                let dateObj  = new Date(d.sessionDate);
                d.sessionDateFormatted = fullDateFormat(dateObj);
                d.sessionYear = +yearFormat(dateObj);
                d.sessionMonth = monthFormat(dateObj);
                console.log(typeof(d.hollowness));
                d.sessionDay = dayFormat(dateObj);

            });

            const ttx = crossfilter(surfData);

            // create dimensions (x-axis values)
            var hollowFactorDim  = ttx.dimension(pluck("hollowness"));
            var crowdFactorDim  = ttx.dimension(pluck("crowdedness"));
            var funFactorDim  = ttx.dimension(pluck("funFactor"));
            var yearDim  = ttx.dimension(pluck("sessionYear"));
            var monthDim  = ttx.dimension(pluck("sessionMonth"));
            var dayDim  = ttx.dimension(pluck("sessionDay"));

            // create groups (y-axis values)
            var all = ttx.groupAll();
            var countPerHollowFactor = hollowFactorDim.group().reduceCount();
            var countPerCrowdFactor = crowdFactorDim.group().reduceCount();
            var countPerFunFactor = funFactorDim.group().reduceCount();
            var countPerYear = yearDim.group().reduceCount();
            var countPerMonth = monthDim.group().reduceCount();
            var countPerDay = dayDim.group().reduceCount();

            const hollowFactorChart = barChart('#chart-bar-hollow-factor');
            const crowdFactorChart = barChart('#chart-bar-crowd-factor');
            const funFactorChart = barChart('#chart-bar-fun-factor');
            const yearChart = pieChart('#chart-ring-year');
            const monthChart = pieChart('#chart-ring-month');
            const dayChart = pieChart('#chart-ring-day');

            hollowFactorChart
              .width(300)
              .height(180)
              .dimension(hollowFactorDim)
              .group(countPerHollowFactor)
              .x(d3.scale.linear().domain([0,5.2]))
              .elasticY(true)
              .centerBar(true)
              .barPadding(5)
              .xAxisLabel('Hollow Factor')
              .yAxisLabel('Sessions')
            hollowFactorChart.xAxis().tickValues([0,1,2,3,4,5]);

            crowdFactorChart
              .width(300)
              .height(180)
              .dimension(crowdFactorDim)
              .group(countPerCrowdFactor)
              .x(d3.scale.linear().domain([0,5.2]))
              .elasticY(true)
              .centerBar(true)
              .barPadding(5)
              .xAxisLabel('Crowd Factor')
              .yAxisLabel('Sessions')
            crowdFactorChart.xAxis().tickValues([0,1,2,3,4,5]);

            funFactorChart
              .width(300)
              .height(180)
              .dimension(funFactorDim)
              .group(countPerFunFactor)
              .x(d3.scale.linear().domain([0,5.2]))
              .elasticY(true)
              .centerBar(true)
              .barPadding(5)
              .xAxisLabel('Fun Factor')
              .yAxisLabel('Sessions')
            funFactorChart.xAxis().tickValues([0, 1, 2, 3, 4, 5]);

            yearChart
              .width(150)
              .height(150)
              .dimension(yearDim)
              .group(countPerYear)
              .innerRadius(20);

            monthChart
              .width(150)
              .height(150)
              .dimension(monthDim)
              .group(countPerMonth)
              .innerRadius(20)
              .ordering(function (d) {
                var order = {
                  'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4,
                  'may': 5, 'jun': 6, 'jul': 7, 'aug': 8,
                  'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12
                };
                return order[d.key];
              });

            dayChart
              .width(150)
              .height(150)
              .dimension(dayDim)
              .group(countPerDay)
              .innerRadius(20)
              .ordering(function (d) {
                var order = {
                  'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3,
                  'fri': 4, 'sat': 5, 'sun': 6
                }
                return order[d.key];
              });

            //draw the viz!
            renderAll();

        });
    }
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import NestedComponent from '../_classes/nested/NestedComponent';
import Component from '../_classes/component/Component';

export default class ContainerComponent extends NestedComponent {
  static schema(...extend) {
    return NestedComponent.schema({
      label: 'Container',
      type: 'container',
      key: 'container',
      clearOnHide: true,
      input: true,
      tree: true,
      components: []
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Container',
      icon: 'folder-open',
      group: 'data',
      documentation: 'http://help.form.io/userguide/#container',
      weight: 10,
      schema: ContainerComponent.schema()
    };
  }

  constructor(...args) {
    super(...args);
    this.type = 'container';
  }

  init() {
    this.components = this.components || [];
    this.addComponents(this.dataValue);
  }

  get defaultSchema() {
    return ContainerComponent.schema();
  }

  get emptyValue() {
    return {};
  }

  get templateName() {
    return 'container';
  }

  hasChanged(before, after) {
    return !_.isEqual(before, after);
  }

  getValue() {
    return this.dataValue;
  }

  updateValue(flags, value) {
    // Intentionally skip over nested component updateValue method to keep recursive update from occurring with sub components.
    return Component.prototype.updateValue.call(this, flags, value);
  }

  setValue(value, flags) {
    flags = this.getFlags.apply(this, arguments);
    if (!value || !_.isObject(value)) {
      return;
    }
    const hasValue = this.hasValue();
    if (hasValue && _.isEmpty(this.dataValue)) {
      flags.noValidate = true;
    }
    if (!hasValue) {
      // Set the data value and then reset each component to use the new data object.
      this.dataValue = {};
    }
    return super.setValue(value, flags);
  }
}
<commit_msg>Default to hide label on container.<commit_after>import _ from 'lodash';
import NestedComponent from '../_classes/nested/NestedComponent';
import Component from '../_classes/component/Component';

export default class ContainerComponent extends NestedComponent {
  static schema(...extend) {
    return NestedComponent.schema({
      label: 'Container',
      type: 'container',
      key: 'container',
      clearOnHide: true,
      input: true,
      tree: true,
      hideLabel: true,
      components: []
    }, ...extend);
  }

  static get builderInfo() {
    return {
      title: 'Container',
      icon: 'folder-open',
      group: 'data',
      documentation: 'http://help.form.io/userguide/#container',
      weight: 10,
      schema: ContainerComponent.schema()
    };
  }

  constructor(...args) {
    super(...args);
    this.type = 'container';
  }

  init() {
    this.components = this.components || [];
    this.addComponents(this.dataValue);
  }

  get defaultSchema() {
    return ContainerComponent.schema();
  }

  get emptyValue() {
    return {};
  }

  get templateName() {
    return 'container';
  }

  hasChanged(before, after) {
    return !_.isEqual(before, after);
  }

  getValue() {
    return this.dataValue;
  }

  updateValue(flags, value) {
    // Intentionally skip over nested component updateValue method to keep recursive update from occurring with sub components.
    return Component.prototype.updateValue.call(this, flags, value);
  }

  setValue(value, flags) {
    flags = this.getFlags.apply(this, arguments);
    if (!value || !_.isObject(value)) {
      return;
    }
    const hasValue = this.hasValue();
    if (hasValue && _.isEmpty(this.dataValue)) {
      flags.noValidate = true;
    }
    if (!hasValue) {
      // Set the data value and then reset each component to use the new data object.
      this.dataValue = {};
    }
    return super.setValue(value, flags);
  }
}
<|endoftext|>


<commit_before>import React, { useRef } from 'react';
import { Box, NumericInput } from '../../';
import theme from './theme.css';

const transformToPaddedNumber = (number) => (number < 10 ? `0${number}` : number.toString());

const DurationInput = ({ value, onChange, onBlur, onFocus, onKeyDown, autoFocus, textAlignRight, className }) => {
  const ref = useRef();

  const handleHoursChanged = (_, hours) => {
    if (!onChange) {
      return;
    }

    if (hours === '') {
      if (typeof value?.minutes === 'undefined') {
        onChange();
        return;
      }

      onChange({
        minutes: value?.minutes,
      });
      return;
    }

    const parsedHours = parseInt(hours);
    if (!Number.isInteger(parsedHours)) {
      return;
    }

    onChange({
      ...value,
      hours: parsedHours,
    });
  };

  const handleMinutesChange = (_, minutes) => {
    if (!onChange) {
      return;
    }

    if (minutes === '') {
      if (typeof value?.hours === 'undefined') {
        onChange();
        return;
      }

      onChange({
        hours: value?.hours,
      });
      return;
    }

    const parsedMinutes = parseInt(minutes);
    if (!Number.isInteger(parsedMinutes)) {
      return;
    }

    if (parsedMinutes > 60) {
      return;
    }

    if (parsedMinutes === 60) {
      onChange({
        hours: (value?.hours || 0) + 1,
        minutes: 0,
      });
      return;
    }

    if (parsedMinutes < 0) {
      onChange({
        hours: (value?.hours || 1) - 1,
        minutes: 59,
      });
      return;
    }

    onChange({
      ...value,
      minutes: parsedMinutes,
    });
  };

  /**
   * This blur handler is a special one, because this input consists of multiple focus points:
   * the two inputs themselves, and the four increment/decrement buttons
   * we filter out the blur events caused by changing focus between these, so the onBlur event only
   * gets triggered when really blurring outside of the entire component
   */
  const handleBlur = (event) => {
    if (!event.relatedTarget) {
      onBlur && onBlur(event);
      return;
    }

    const inputs = ref.current?.getElementsByTagName('input') || [];
    if (Array.from(inputs).find((node) => node === event.relatedTarget)) {
      return;
    }

    const buttons = ref.current?.getElementsByTagName('button') || [];
    if (Array.from(buttons).find((node) => node === event.relatedTarget)) {
      return;
    }

    onBlur && onBlur(event);
  };

  let minutes = value?.minutes;
  if (Number.isInteger(minutes)) {
    minutes = transformToPaddedNumber(minutes);
  }

  let hours = value?.hours;
  if (Number.isInteger(hours)) {
    hours = transformToPaddedNumber(hours);
  }

  return (
    <Box display="flex" alignItems="center" ref={ref} className={className}>
      <NumericInput
        placeholder="00"
        min={0}
        value={hours ?? ''}
        onChange={handleHoursChanged}
        onBlur={handleBlur}
        onFocus={onFocus}
        onKeyDown={onKeyDown}
        type="text"
        inputMode="numeric"
        className={theme['duration-input-numeric-input']}
        autoFocus={autoFocus}
        textAlignRight={textAlignRight}
      />
      <NumericInput
        placeholder="00"
        {...(!value?.hours ? { min: 0 } : {})}
        value={minutes ?? ''}
        onChange={handleMinutesChange}
        onBlur={handleBlur}
        onFocus={onFocus}
        onKeyDown={onKeyDown}
        type="text"
        inputMode="numeric"
        className={theme['duration-input-numeric-input']}
        textAlignRight={textAlignRight}
      />
    </Box>
  );
};

export default DurationInput;
<commit_msg>:sparkles: increase the minute step to 15<commit_after>import React, { useRef } from 'react';
import { Box, NumericInput } from '../../';
import theme from './theme.css';

const transformToPaddedNumber = (number) => (number < 10 ? `0${number}` : number.toString());

const MINUTES_STEP = 15;

const DurationInput = ({ value, onChange, onBlur, onFocus, onKeyDown, autoFocus, textAlignRight, className }) => {
  const ref = useRef();

  const handleHoursChanged = (_, hours) => {
    if (!onChange) {
      return;
    }

    if (hours === '') {
      if (typeof value?.minutes === 'undefined') {
        onChange();
        return;
      }

      onChange({
        minutes: value?.minutes,
      });
      return;
    }

    const parsedHours = parseInt(hours);
    if (!Number.isInteger(parsedHours)) {
      return;
    }

    onChange({
      ...value,
      hours: parsedHours,
    });
  };

  const handleMinutesChange = (_, minutes) => {
    if (!onChange) {
      return;
    }

    if (minutes === '') {
      if (typeof value?.hours === 'undefined') {
        onChange();
        return;
      }

      onChange({
        hours: value?.hours,
      });
      return;
    }

    const parsedMinutes = parseInt(minutes);
    if (!Number.isInteger(parsedMinutes)) {
      return;
    }

    if (parsedMinutes > 60) {
      return;
    }

    if (parsedMinutes === 60) {
      onChange({
        hours: (value?.hours || 0) + 1,
        minutes: 0,
      });
      return;
    }

    if (parsedMinutes < 0) {
      onChange({
        hours: (value?.hours || 1) - 1,
        minutes: 60 - MINUTES_STEP,
      });
      return;
    }

    onChange({
      ...value,
      minutes: parsedMinutes,
    });
  };

  /**
   * This blur handler is a special one, because this input consists of multiple focus points:
   * the two inputs themselves, and the four increment/decrement buttons
   * we filter out the blur events caused by changing focus between these, so the onBlur event only
   * gets triggered when really blurring outside of the entire component
   */
  const handleBlur = (event) => {
    if (!event.relatedTarget) {
      onBlur && onBlur(event);
      return;
    }

    const inputs = ref.current?.getElementsByTagName('input') || [];
    if (Array.from(inputs).find((node) => node === event.relatedTarget)) {
      return;
    }

    const buttons = ref.current?.getElementsByTagName('button') || [];
    if (Array.from(buttons).find((node) => node === event.relatedTarget)) {
      return;
    }

    onBlur && onBlur(event);
  };

  let minutes = value?.minutes;
  if (Number.isInteger(minutes)) {
    minutes = transformToPaddedNumber(minutes);
  }

  let hours = value?.hours;
  if (Number.isInteger(hours)) {
    hours = transformToPaddedNumber(hours);
  }

  return (
    <Box display="flex" alignItems="center" ref={ref} className={className}>
      <NumericInput
        placeholder="00"
        min={0}
        value={hours ?? ''}
        onChange={handleHoursChanged}
        onBlur={handleBlur}
        onFocus={onFocus}
        onKeyDown={onKeyDown}
        type="text"
        inputMode="numeric"
        className={theme['duration-input-numeric-input']}
        autoFocus={autoFocus}
        textAlignRight={textAlignRight}
      />
      <NumericInput
        placeholder="00"
        step={MINUTES_STEP}
        {...(!value?.hours ? { min: 0 } : {})}
        value={minutes ?? ''}
        onChange={handleMinutesChange}
        onBlur={handleBlur}
        onFocus={onFocus}
        onKeyDown={onKeyDown}
        type="text"
        inputMode="numeric"
        className={theme['duration-input-numeric-input']}
        textAlignRight={textAlignRight}
      />
    </Box>
  );
};

export default DurationInput;
<|endoftext|>


<commit_before>'use strict'

import React from 'react'
import IssuesList from 'components/molecules/issues_list/IssuesList'
import IssuesTabs from 'components/molecules/issues_tabs/IssuesTabs'
import Tab from 'components/atoms/tab/Tab'
import Github from 'github-api'
import GithubQueryBuilder from '../../helpers/GithubQueryBuilder'
import Settings from 'components/atoms/settings/Settings'

_ = require('lodash')
require('./stylesheets/main.scss')

class Main extends React.Component {
  constructor(props) {
    super(props)
    // TODO associate creation of query with tab passing the value on the constructor
    this.state = {
      issues: [],
      activeTab: 'hot',
      githubQuery: new GithubQueryBuilder()
    }
  }
  setGithubQuery() {
    this.state.githubQuery.applyFilter(tab)
  }
  componentDidMount() {
    this.requestGithub()
  }
  changeTab(tab) {
    this.setState({activeTab: tab})
    this.state.githubQuery.applyFilter(tab)
    this.requestGithub()
  }
  requestGithub() {
    let github = new Github({})
    let search = github.getSearch(this.state.githubQuery.getQuery())

    search.issues(null, (err, issues) => {
      this.setState({
        issues: issues.items
      })
      console.log(oldIssues[0])
      console.log(oldIssues[1])
    })
  }
  render() {
    return (

{_.capitalize(this.state.activeTab)} Issues

            <Tab name='hot' onClick={this.changeTab.bind(this)}
               activeTab={this.state.activeTab}/>
            <Tab name='trending' onClick={this.changeTab.bind(this)}
               activeTab={this.state.activeTab} />
            <Tab name='fresh' onClick={this.changeTab.bind(this)}
               activeTab={this.state.activeTab} />

          <Settings></Settings>

        <IssuesList issues={this.state.issues}
          activeTab={this.state.activeTab}/>

    )
  }
}

Main.displayName = 'OrganismMain'

// Uncomment properties you need
// Main.propTypes = {};
// Main.defaultProps = {};

export default Main
<commit_msg>add filters component to main js<commit_after>'use strict'

import React from 'react'
import IssuesList from 'components/molecules/issues_list/IssuesList'
import IssuesTabs from 'components/molecules/issues_tabs/IssuesTabs'
import Tab from 'components/atoms/tab/Tab'
import Github from 'github-api'
import GithubQueryBuilder from '../../helpers/GithubQueryBuilder'
import Settings from 'components/atoms/settings/Settings'
import Filters from 'components/molecules/filters/Filters'
import FontIcon from 'material-ui/lib/font-icon'
import classNames from 'classnames'

_ = require('lodash')
require('./stylesheets/main.scss')

class Main extends React.Component {
  constructor(props) {
    super(props)
    // TODO associate creation of query with tab passing the value on the constructor
    this.state = {
      issues: [],
      activeTab: 'hot',
      githubQuery: new GithubQueryBuilder(),
      currentComponent: 'IssuesList'
    }
  }
  setGithubQuery() {
    this.state.githubQuery.applyFilter(tab)
  }
  componentDidMount() {
    this.requestGithub()
  }
  changeTab(tab) {
    this.setState({activeTab: tab})
    this.state.githubQuery.applyFilter(tab)
    this.requestGithub()
  }
  requestGithub() {
    let github = new Github({})
    let search = github.getSearch(this.state.githubQuery.getQuery())

    search.issues(null, (err, issues) => {
      this.setState({
        issues: issues.items
      })
    })
  }
  currentComponentClass(component) {
    let componentClass = classNames({
      'component': true,
      'active': this.state.currentComponent == component
    })
    return componentClass
  }
  changeCurrentComponent(component) {
    this.setState({currentComponent: component})
  }
  render() {
    return (

{_.capitalize(this.state.activeTab)} Issues

              <Tab name='hot' onClick={this.changeTab.bind(this)}
                 activeTab={this.state.activeTab}/>
              <Tab name='trending' onClick={this.changeTab.bind(this)}
                 activeTab={this.state.activeTab} />
              <Tab name='fresh' onClick={this.changeTab.bind(this)}
                 activeTab={this.state.activeTab} />

            <Settings changeComponentTo='Filters'
                      onClick={this.changeCurrentComponent.bind(this)}>
            </Settings>

          <IssuesList issues={this.state.issues}
            activeTab={this.state.activeTab}/>

          <Filters changeComponentTo='IssuesList'
                   onClick={this.changeCurrentComponent.bind(this)}>
          </Filters>

    )
  }
}

Main.displayName = 'OrganismMain'

// Uncomment properties you need
// Main.propTypes = {};
// Main.defaultProps = {};

export default Main
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import * as React from 'react';
import { timeCode } from 'firefox-profiler/utils/time-code';
import classNames from 'classnames';
import { Tooltip } from 'firefox-profiler/components/tooltip/Tooltip';

import type { CssPixels, DevicePixels } from 'firefox-profiler/types';

type Props<HoveredItem> = {|
  +containerWidth: CssPixels,
  +containerHeight: CssPixels,
  +className: string,
  +onSelectItem?: (HoveredItem | null) => void,
  +onRightClick?: (HoveredItem | null) => void,
  +onDoubleClickItem: (HoveredItem | null) => void,
  +getHoveredItemInfo: HoveredItem => React.Node,
  +drawCanvas: (
    CanvasRenderingContext2D,
    hoveredItem: HoveredItem | null,
    prevHoveredItem: HoveredItem | null,
    isHoveredOnlyDifferent: boolean
  ) => void,
  +isDragging: boolean,
  // Applies ctx.scale() to the canvas to draw using CssPixels rather than DevicePixels.
  +scaleCtxToCssPixels: boolean,
  +hitTest: (x: CssPixels, y: CssPixels) => HoveredItem | null,
|};

// The naming of the X and Y coordinates here correspond to the ones
// found on the MouseEvent interface.
type State<HoveredItem> = {
  hoveredItem: HoveredItem | null,
  pageX: CssPixels,
  pageY: CssPixels,
};

require('./Canvas.css');

/**
 * The maximum amount of movement in either direction between the
 * mouse down and mouse up event for it to be interpreted as a
 * item-selecting click. We cannot use a real click event as a trigger
 * for selecting items because then a long dragging movement of the
 * viewport would still select items when the mouse button is
 * released. On the other hand, for accessibility reasons we want a
 * small amount of movement between mouse down and up to be okay.
 */
const MOUSE_CLICK_MAX_MOVEMENT_DELTA: CssPixels = 5;

// This isn't a PureComponent on purpose: we always want to update if the parent updates
// But we still conditionally update the canvas itself, see componentDidUpdate.
export default class ChartCanvas<HoveredItem> extends React.Component<
  Props<HoveredItem>,
  State<HoveredItem>
> {
  _devicePixelRatio: number = 1;
  // The current mouse position. Needs to be stored for tooltip
  // hit-test if props update.
  _offsetX: CssPixels = 0;
  _offsetY: CssPixels = 0;
  // The position of the most recent mouse down event. Needed for
  // comparison with the current mouse position in order to
  // distinguish between clicks and drags.
  _mouseDownOffsetX: CssPixels = 0;
  _mouseDownOffsetY: CssPixels = 0;
  // Indicates if move threshold breached. Checked at mouse up event
  // to prevent it from being interpreted as a click.
  _mouseMovedWhileClicked: boolean = false;
  _ctx: CanvasRenderingContext2D;
  _canvas: HTMLCanvasElement | null = null;
  _isDrawScheduled: boolean = false;

  state: State<HoveredItem> = {
    hoveredItem: null,
    pageX: 0,
    pageY: 0,
  };

  _scheduleDraw(
    isHoveredOnlyDifferent: boolean = false,
    prevHoveredItem: HoveredItem | null = null
  ) {
    const { className, drawCanvas } = this.props;
    if (this._isDrawScheduled) {
      return;
    }
    this._isDrawScheduled = true;
    window.requestAnimationFrame(() => {
      this._isDrawScheduled = false;
      if (this._canvas) {
        timeCode(`${className} render`, () => {
          this._prepCanvas();
          drawCanvas(
            this._ctx,
            this.state.hoveredItem,
            prevHoveredItem,
            isHoveredOnlyDifferent
          );
        });
      }
    });
  }

  _prepCanvas() {
    const canvas = this._canvas;
    const { containerWidth, containerHeight, scaleCtxToCssPixels } = this.props;
    const { devicePixelRatio } = window;

    if (!canvas) {
      return;
    }

    let ctx = this._ctx;
    if (!ctx) {
      ctx = canvas.getContext('2d', { alpha: false });
      this._ctx = ctx;
    }

    // The DevicePixels for the canvas are integers, but the CSS styles use float values.
    // Use the "floor" function to make sure we assign the device pixel values how we
    // expect.
    const devicePixelWidth: DevicePixels = Math.floor(
      containerWidth * devicePixelRatio
    );
    const devicePixelHeight: DevicePixels = Math.floor(
      containerHeight * devicePixelRatio
    );

    if (
      canvas.width !== devicePixelWidth ||
      canvas.height !== devicePixelHeight
    ) {
      // The canvas needs to be sized to the container.
      canvas.width = devicePixelWidth;
      canvas.height = devicePixelHeight;
      // Round the style width as well, so the canvas will be sized according to
      // the integer pixel size.
      // TODO #2370 - We should review the correctness of this approach, to make sure
      // we don't have any blank pixels at the edge, or that we overflow.
      canvas.style.width = Math.floor(containerWidth) + 'px';
      canvas.style.height = Math.floor(containerHeight) + 'px';
      if (scaleCtxToCssPixels) {
        ctx.scale(this._devicePixelRatio, this._devicePixelRatio);
      }
    }

    if (this._devicePixelRatio !== devicePixelRatio) {
      if (scaleCtxToCssPixels) {
        // Make sure and multiply by the inverse of the previous ratio, as the scaling
        // operates off of the previous set scale.
        const scale = (1 / this._devicePixelRatio) * devicePixelRatio;
        ctx.scale(scale, scale);
      }
      this._devicePixelRatio = devicePixelRatio;
    }
  }

  _onMouseDown = (e: { nativeEvent: MouseEvent } & SyntheticMouseEvent<>) => {
    if (e.button === 0) {
      // Remember where the mouse was positioned. Move too far and it
      // won't be registered as a selecting click on mouse up.
      this._mouseDownOffsetX = e.nativeEvent.offsetX;
      this._mouseDownOffsetY = e.nativeEvent.offsetY;
      this._mouseMovedWhileClicked = false;
    }

    if (e.button === 2 && this.props.onRightClick) {
      // The right button is a contextual action.
      // It is important that we call the right click callback at mousedown so
      // that the state is updated and the context menus are rendered before the
      // mouseup/contextmenu events.
      this.props.onRightClick(this.state.hoveredItem);
    }
  };

  _onMouseUp = (e: SyntheticMouseEvent<>) => {
    if (this._mouseMovedWhileClicked) {
      return;
    }

    if (e.button === 0 && this.props.onSelectItem) {
      // Left button is a selection action
      this.props.onSelectItem(this.state.hoveredItem);
    }
  };

  _onMouseMove = (
    event: { nativeEvent: MouseEvent } & SyntheticMouseEvent<>
  ) => {
    if (!this._canvas) {
      return;
    }

    this._offsetX = event.nativeEvent.offsetX;
    this._offsetY = event.nativeEvent.offsetY;
    // event.buttons is a bitfield representing which buttons are pressed at the
    // time of the mousemove event. The first bit is for the left click.
    // This operation checks if the left button is clicked, but this will also
    // be true if any other button is clickes as well.
    const hasLeftClick = (event.buttons & 1) !== 0;

    // If the mouse moves too far while the primary button is down, flag this as
    // drag event only. Then it won't select anything when the button is
    // released.
    if (
      !this._mouseMovedWhileClicked &&
      hasLeftClick &&
      (Math.abs(this._offsetX - this._mouseDownOffsetX) >
        MOUSE_CLICK_MAX_MOVEMENT_DELTA ||
        Math.abs(this._offsetY - this._mouseDownOffsetY) >
          MOUSE_CLICK_MAX_MOVEMENT_DELTA)
    ) {
      this._mouseMovedWhileClicked = true;
    }

    const maybeHoveredItem = this.props.hitTest(this._offsetX, this._offsetY);
    if (maybeHoveredItem !== null) {
      this.setState({
        hoveredItem: maybeHoveredItem,
        pageX: event.pageX,
        pageY: event.pageY,
      });
    } else if (
      this.state.hoveredItem !== null &&
      // This persistTooltips property is part of the web console API. It helps
      // in being able to inspect and debug tooltips.
      !window.persistTooltips
    ) {
      this.setState({
        hoveredItem: null,
      });
    }
  };

  _onMouseOut = () => {
    if (
      this.state.hoveredItem !== null &&
      // This persistTooltips property is part of the web console API. It helps
      // in being able to inspect and debug tooltips.
      !window.persistTooltips
    ) {
      this.setState({ hoveredItem: null });
    }
  };

  _onDoubleClick = () => {
    this.props.onDoubleClickItem(this.state.hoveredItem);
  };

  _getHoveredItemInfo = (): React.Node => {
    const { hoveredItem } = this.state;
    if (hoveredItem === null) {
      return null;
    }
    return this.props.getHoveredItemInfo(hoveredItem);
  };

  _takeCanvasRef = (canvas: HTMLCanvasElement | null) => {
    this._canvas = canvas;
  };

  UNSAFE_componentWillReceiveProps() {
    // It is possible that the data backing the chart has been
    // changed, for instance after symbolication. Clear the
    // hoveredItem if the mouse no longer hovers over it.
    const { hoveredItem } = this.state;
    if (
      hoveredItem !== null &&
      !hoveredItemsAreEqual(
        this.props.hitTest(this._offsetX, this._offsetY),
        hoveredItem
      )
    ) {
      this.setState({ hoveredItem: null });
    }
  }

  componentDidUpdate(
    prevProps: Props<HoveredItem>,
    prevState: State<HoveredItem>
  ) {
    if (prevProps !== this.props) {
      this._scheduleDraw();
    } else if (
      !hoveredItemsAreEqual(prevState.hoveredItem, this.state.hoveredItem)
    ) {
      // Only the hovered items changed.
      this._scheduleDraw(true, prevState.hoveredItem);
    }
  }

  render() {
    const { isDragging } = this.props;
    const { hoveredItem, pageX, pageY } = this.state;

    const className = classNames({
      chartCanvas: true,
      [this.props.className]: true,
      hover: hoveredItem !== null,
    });

    const tooltipContents = this._getHoveredItemInfo();

    return (
      <div>
        <canvas
          className={className}
          ref={this._takeCanvasRef}
          onMouseDown={this._onMouseDown}
          onMouseUp={this._onMouseUp}
          onMouseMove={this._onMouseMove}
          onMouseOut={this._onMouseOut}
          onDoubleClick={this._onDoubleClick}
        />
        {!isDragging && tooltipContents ? (
          <Tooltip mouseX={pageX} mouseY={pageY}>
            {tooltipContents}
          </Tooltip>
        ) : null}
      </div>
    );
  }
}

/**
 * Check for shallow equality for objects, and strict equality for everything else.
 */
function hoveredItemsAreEqual(a: any, b: any) {
  if (a && b && typeof a === 'object' && typeof b === 'object') {
    if (a.length !== b.length) {
      return false;
    }
    let hasAllKeys = true;
    for (const aKey in a) {
      let hasKey = false;
      for (const bKey in b) {
        if (aKey === bKey) {
          if (a[aKey] !== b[bKey]) {
            return false;
          }
          hasKey = true;
          break;
        }
      }
      hasAllKeys = hasAllKeys && hasKey;
      if (!hasAllKeys) {
        return false;
      }
    }
    return true;
  }
  return a === b;
}
<commit_msg>Change require to import in Canvas.js<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @flow
import * as React from 'react';
import { timeCode } from 'firefox-profiler/utils/time-code';
import classNames from 'classnames';
import { Tooltip } from 'firefox-profiler/components/tooltip/Tooltip';

import type { CssPixels, DevicePixels } from 'firefox-profiler/types';

type Props<HoveredItem> = {|
  +containerWidth: CssPixels,
  +containerHeight: CssPixels,
  +className: string,
  +onSelectItem?: (HoveredItem | null) => void,
  +onRightClick?: (HoveredItem | null) => void,
  +onDoubleClickItem: (HoveredItem | null) => void,
  +getHoveredItemInfo: HoveredItem => React.Node,
  +drawCanvas: (
    CanvasRenderingContext2D,
    hoveredItem: HoveredItem | null,
    prevHoveredItem: HoveredItem | null,
    isHoveredOnlyDifferent: boolean
  ) => void,
  +isDragging: boolean,
  // Applies ctx.scale() to the canvas to draw using CssPixels rather than DevicePixels.
  +scaleCtxToCssPixels: boolean,
  +hitTest: (x: CssPixels, y: CssPixels) => HoveredItem | null,
|};

// The naming of the X and Y coordinates here correspond to the ones
// found on the MouseEvent interface.
type State<HoveredItem> = {
  hoveredItem: HoveredItem | null,
  pageX: CssPixels,
  pageY: CssPixels,
};

import './Canvas.css';

/**
 * The maximum amount of movement in either direction between the
 * mouse down and mouse up event for it to be interpreted as a
 * item-selecting click. We cannot use a real click event as a trigger
 * for selecting items because then a long dragging movement of the
 * viewport would still select items when the mouse button is
 * released. On the other hand, for accessibility reasons we want a
 * small amount of movement between mouse down and up to be okay.
 */
const MOUSE_CLICK_MAX_MOVEMENT_DELTA: CssPixels = 5;

// This isn't a PureComponent on purpose: we always want to update if the parent updates
// But we still conditionally update the canvas itself, see componentDidUpdate.
export default class ChartCanvas<HoveredItem> extends React.Component<
  Props<HoveredItem>,
  State<HoveredItem>
> {
  _devicePixelRatio: number = 1;
  // The current mouse position. Needs to be stored for tooltip
  // hit-test if props update.
  _offsetX: CssPixels = 0;
  _offsetY: CssPixels = 0;
  // The position of the most recent mouse down event. Needed for
  // comparison with the current mouse position in order to
  // distinguish between clicks and drags.
  _mouseDownOffsetX: CssPixels = 0;
  _mouseDownOffsetY: CssPixels = 0;
  // Indicates if move threshold breached. Checked at mouse up event
  // to prevent it from being interpreted as a click.
  _mouseMovedWhileClicked: boolean = false;
  _ctx: CanvasRenderingContext2D;
  _canvas: HTMLCanvasElement | null = null;
  _isDrawScheduled: boolean = false;

  state: State<HoveredItem> = {
    hoveredItem: null,
    pageX: 0,
    pageY: 0,
  };

  _scheduleDraw(
    isHoveredOnlyDifferent: boolean = false,
    prevHoveredItem: HoveredItem | null = null
  ) {
    const { className, drawCanvas } = this.props;
    if (this._isDrawScheduled) {
      return;
    }
    this._isDrawScheduled = true;
    window.requestAnimationFrame(() => {
      this._isDrawScheduled = false;
      if (this._canvas) {
        timeCode(`${className} render`, () => {
          this._prepCanvas();
          drawCanvas(
            this._ctx,
            this.state.hoveredItem,
            prevHoveredItem,
            isHoveredOnlyDifferent
          );
        });
      }
    });
  }

  _prepCanvas() {
    const canvas = this._canvas;
    const { containerWidth, containerHeight, scaleCtxToCssPixels } = this.props;
    const { devicePixelRatio } = window;

    if (!canvas) {
      return;
    }

    let ctx = this._ctx;
    if (!ctx) {
      ctx = canvas.getContext('2d', { alpha: false });
      this._ctx = ctx;
    }

    // The DevicePixels for the canvas are integers, but the CSS styles use float values.
    // Use the "floor" function to make sure we assign the device pixel values how we
    // expect.
    const devicePixelWidth: DevicePixels = Math.floor(
      containerWidth * devicePixelRatio
    );
    const devicePixelHeight: DevicePixels = Math.floor(
      containerHeight * devicePixelRatio
    );

    if (
      canvas.width !== devicePixelWidth ||
      canvas.height !== devicePixelHeight
    ) {
      // The canvas needs to be sized to the container.
      canvas.width = devicePixelWidth;
      canvas.height = devicePixelHeight;
      // Round the style width as well, so the canvas will be sized according to
      // the integer pixel size.
      // TODO #2370 - We should review the correctness of this approach, to make sure
      // we don't have any blank pixels at the edge, or that we overflow.
      canvas.style.width = Math.floor(containerWidth) + 'px';
      canvas.style.height = Math.floor(containerHeight) + 'px';
      if (scaleCtxToCssPixels) {
        ctx.scale(this._devicePixelRatio, this._devicePixelRatio);
      }
    }

    if (this._devicePixelRatio !== devicePixelRatio) {
      if (scaleCtxToCssPixels) {
        // Make sure and multiply by the inverse of the previous ratio, as the scaling
        // operates off of the previous set scale.
        const scale = (1 / this._devicePixelRatio) * devicePixelRatio;
        ctx.scale(scale, scale);
      }
      this._devicePixelRatio = devicePixelRatio;
    }
  }

  _onMouseDown = (e: { nativeEvent: MouseEvent } & SyntheticMouseEvent<>) => {
    if (e.button === 0) {
      // Remember where the mouse was positioned. Move too far and it
      // won't be registered as a selecting click on mouse up.
      this._mouseDownOffsetX = e.nativeEvent.offsetX;
      this._mouseDownOffsetY = e.nativeEvent.offsetY;
      this._mouseMovedWhileClicked = false;
    }

    if (e.button === 2 && this.props.onRightClick) {
      // The right button is a contextual action.
      // It is important that we call the right click callback at mousedown so
      // that the state is updated and the context menus are rendered before the
      // mouseup/contextmenu events.
      this.props.onRightClick(this.state.hoveredItem);
    }
  };

  _onMouseUp = (e: SyntheticMouseEvent<>) => {
    if (this._mouseMovedWhileClicked) {
      return;
    }

    if (e.button === 0 && this.props.onSelectItem) {
      // Left button is a selection action
      this.props.onSelectItem(this.state.hoveredItem);
    }
  };

  _onMouseMove = (
    event: { nativeEvent: MouseEvent } & SyntheticMouseEvent<>
  ) => {
    if (!this._canvas) {
      return;
    }

    this._offsetX = event.nativeEvent.offsetX;
    this._offsetY = event.nativeEvent.offsetY;
    // event.buttons is a bitfield representing which buttons are pressed at the
    // time of the mousemove event. The first bit is for the left click.
    // This operation checks if the left button is clicked, but this will also
    // be true if any other button is clickes as well.
    const hasLeftClick = (event.buttons & 1) !== 0;

    // If the mouse moves too far while the primary button is down, flag this as
    // drag event only. Then it won't select anything when the button is
    // released.
    if (
      !this._mouseMovedWhileClicked &&
      hasLeftClick &&
      (Math.abs(this._offsetX - this._mouseDownOffsetX) >
        MOUSE_CLICK_MAX_MOVEMENT_DELTA ||
        Math.abs(this._offsetY - this._mouseDownOffsetY) >
          MOUSE_CLICK_MAX_MOVEMENT_DELTA)
    ) {
      this._mouseMovedWhileClicked = true;
    }

    const maybeHoveredItem = this.props.hitTest(this._offsetX, this._offsetY);
    if (maybeHoveredItem !== null) {
      this.setState({
        hoveredItem: maybeHoveredItem,
        pageX: event.pageX,
        pageY: event.pageY,
      });
    } else if (
      this.state.hoveredItem !== null &&
      // This persistTooltips property is part of the web console API. It helps
      // in being able to inspect and debug tooltips.
      !window.persistTooltips
    ) {
      this.setState({
        hoveredItem: null,
      });
    }
  };

  _onMouseOut = () => {
    if (
      this.state.hoveredItem !== null &&
      // This persistTooltips property is part of the web console API. It helps
      // in being able to inspect and debug tooltips.
      !window.persistTooltips
    ) {
      this.setState({ hoveredItem: null });
    }
  };

  _onDoubleClick = () => {
    this.props.onDoubleClickItem(this.state.hoveredItem);
  };

  _getHoveredItemInfo = (): React.Node => {
    const { hoveredItem } = this.state;
    if (hoveredItem === null) {
      return null;
    }
    return this.props.getHoveredItemInfo(hoveredItem);
  };

  _takeCanvasRef = (canvas: HTMLCanvasElement | null) => {
    this._canvas = canvas;
  };

  UNSAFE_componentWillReceiveProps() {
    // It is possible that the data backing the chart has been
    // changed, for instance after symbolication. Clear the
    // hoveredItem if the mouse no longer hovers over it.
    const { hoveredItem } = this.state;
    if (
      hoveredItem !== null &&
      !hoveredItemsAreEqual(
        this.props.hitTest(this._offsetX, this._offsetY),
        hoveredItem
      )
    ) {
      this.setState({ hoveredItem: null });
    }
  }

  componentDidUpdate(
    prevProps: Props<HoveredItem>,
    prevState: State<HoveredItem>
  ) {
    if (prevProps !== this.props) {
      this._scheduleDraw();
    } else if (
      !hoveredItemsAreEqual(prevState.hoveredItem, this.state.hoveredItem)
    ) {
      // Only the hovered items changed.
      this._scheduleDraw(true, prevState.hoveredItem);
    }
  }

  render() {
    const { isDragging } = this.props;
    const { hoveredItem, pageX, pageY } = this.state;

    const className = classNames({
      chartCanvas: true,
      [this.props.className]: true,
      hover: hoveredItem !== null,
    });

    const tooltipContents = this._getHoveredItemInfo();

    return (
      <div>
        <canvas
          className={className}
          ref={this._takeCanvasRef}
          onMouseDown={this._onMouseDown}
          onMouseUp={this._onMouseUp}
          onMouseMove={this._onMouseMove}
          onMouseOut={this._onMouseOut}
          onDoubleClick={this._onDoubleClick}
        />
        {!isDragging && tooltipContents ? (
          <Tooltip mouseX={pageX} mouseY={pageY}>
            {tooltipContents}
          </Tooltip>
        ) : null}
      </div>
    );
  }
}

/**
 * Check for shallow equality for objects, and strict equality for everything else.
 */
function hoveredItemsAreEqual(a: any, b: any) {
  if (a && b && typeof a === 'object' && typeof b === 'object') {
    if (a.length !== b.length) {
      return false;
    }
    let hasAllKeys = true;
    for (const aKey in a) {
      let hasKey = false;
      for (const bKey in b) {
        if (aKey === bKey) {
          if (a[aKey] !== b[bKey]) {
            return false;
          }
          hasKey = true;
          break;
        }
      }
      hasAllKeys = hasAllKeys && hasKey;
      if (!hasAllKeys) {
        return false;
      }
    }
    return true;
  }
  return a === b;
}
<|endoftext|>


<commit_before>import React from 'react'
import {connect} from 'react-redux'
import {Route, Switch} from 'react-router'
import {NavLink} from 'react-router-dom'
import {compose, withProps} from 'recompose'
import styled from 'styled-components'

import {updateUser} from '../../actions/users'
import {withDatabaseSubscribe, withLoading, withNotFound} from '../hocs'
import LoadingView from '../LoadingView'
import NotFoundView from '../NotFoundView'
import UserFollowers from './UserFollowers'
import UserFollowing from './UserFollowing'
import UserGroups from './UserGroups'
import UserOverview from './UserOverview'
import UserPlaylists from './UserPlaylists'
import UserSeries from './UserSeries'
import UserShows from './UserShows'
import UserVideosView from './UserVideosView'

const Container = styled.div`
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
`

const mapStateToProps = ({users}) => ({
  users
})

const enhance = compose(
  connect(mapStateToProps),
  withProps(({match}) => ({
    basePath: match.path,
    baseUrl: match.url,
    userId: match.params.userId
  })),
  withDatabaseSubscribe(
    'value',
    (props) => (`users/${props.userId}`),
    (props) => (snapshot) => (
      props.dispatch(updateUser(
        {
          userId: props.userId,
          userSnapshot: snapshot.val()
        })
      )
    )
  ),
  withLoading(
    (props) => !(props.userId in props.users),
    LoadingView
  ),
  withNotFound(
    (props) => (props.users[props.userId] === null),
    NotFoundView
  ),
)

export const navLinks = {
  overview: 'Overview',
  videos: 'Videos',
  groups: 'Groups',
  following: 'Following',
  followers: 'Followers',
  shows: 'Shows',
  series: 'Series',
  playlists: 'Playlists',
}

const UserContainer = ({basePath, baseUrl, children, userId, users}) => (
  <Container>
    <img
      src='http://placekitten.com/g/200/200'
      alt={`${users[userId].username}`}
    />

      <NavLink to={baseUrl}>{navLinks.overview}</NavLink>
      <NavLink to={`${baseUrl}/videos`}>{navLinks.videos}</NavLink>
      <NavLink to={`${baseUrl}/groups`}>{navLinks.groups}</NavLink>
      <NavLink to={`${baseUrl}/following`}>{navLinks.following}</NavLink>
      <NavLink to={`${baseUrl}/followers`}>{navLinks.followers}</NavLink>
      <NavLink to={`${baseUrl}/shows`}>{navLinks.shows}</NavLink>
      <NavLink to={`${baseUrl}/series`}>{navLinks.series}</NavLink>
      <NavLink to={`${baseUrl}/playlists`}>{navLinks.playlists}</NavLink>

    <Switch>
      <Route path={`${basePath}/videos`} component={UserVideosView}/>
      <Route path={`${basePath}/groups`} component={UserGroups}/>
      <Route path={`${basePath}/following`} component={UserFollowing}/>
      <Route path={`${basePath}/followers`} component={UserFollowers}/>
      <Route path={`${basePath}/shows`} component={UserShows}/>
      <Route path={`${basePath}/series`} component={UserSeries}/>
      <Route path={`${basePath}/playlists`} component={UserPlaylists}/>
      <Route path={`${basePath}/`} component={UserOverview}/>
    </Switch>
  </Container>
)

export default enhance(UserContainer)<commit_msg>Use multiple flexboxes to contain different parts<commit_after>import React from 'react'
import {connect} from 'react-redux'
import {Route, Switch} from 'react-router'
import {NavLink} from 'react-router-dom'
import {compose, withProps} from 'recompose'
import styled from 'styled-components'

import {updateUser} from '../../actions/users'
import {withDatabaseSubscribe, withLoading, withNotFound} from '../hocs'
import LoadingView from '../LoadingView'
import NotFoundView from '../NotFoundView'
import UserFollowers from './UserFollowers'
import UserFollowing from './UserFollowing'
import UserGroups from './UserGroups'
import UserOverview from './UserOverview'
import UserPlaylists from './UserPlaylists'
import UserSeries from './UserSeries'
import UserShows from './UserShows'
import UserVideosView from './UserVideosView'

const Container = styled.div`
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
`

const NonSwitchedContent = styled.div`
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
`

const mapStateToProps = ({users}) => ({
  users
})

const enhance = compose(
  connect(mapStateToProps),
  withProps(({match}) => ({
    basePath: match.path,
    baseUrl: match.url,
    userId: match.params.userId
  })),
  withDatabaseSubscribe(
    'value',
    (props) => (`users/${props.userId}`),
    (props) => (snapshot) => (
      props.dispatch(updateUser(
        {
          userId: props.userId,
          userSnapshot: snapshot.val()
        })
      )
    )
  ),
  withLoading(
    (props) => !(props.userId in props.users),
    LoadingView
  ),
  withNotFound(
    (props) => (props.users[props.userId] === null),
    NotFoundView
  ),
)

export const navLinks = {
  overview: 'Overview',
  videos: 'Videos',
  groups: 'Groups',
  following: 'Following',
  followers: 'Followers',
  shows: 'Shows',
  series: 'Series',
  playlists: 'Playlists',
}

const UserContainer = ({basePath, baseUrl, children, userId, users}) => (
  <Container>
    <NonSwitchedContent>
      <img
        src='http://placekitten.com/g/200/200'
        alt={`${users[userId].username}`}
      />

        <NavLink to={baseUrl}>{navLinks.overview}</NavLink>
        <NavLink to={`${baseUrl}/videos`}>{navLinks.videos}</NavLink>
        <NavLink to={`${baseUrl}/groups`}>{navLinks.groups}</NavLink>
        <NavLink to={`${baseUrl}/following`}>{navLinks.following}</NavLink>
        <NavLink to={`${baseUrl}/followers`}>{navLinks.followers}</NavLink>
        <NavLink to={`${baseUrl}/shows`}>{navLinks.shows}</NavLink>
        <NavLink to={`${baseUrl}/series`}>{navLinks.series}</NavLink>
        <NavLink to={`${baseUrl}/playlists`}>{navLinks.playlists}</NavLink>

    </NonSwitchedContent>
    <Switch>
      <Route path={`${basePath}/videos`} component={UserVideosView}/>
      <Route path={`${basePath}/groups`} component={UserGroups}/>
      <Route path={`${basePath}/following`} component={UserFollowing}/>
      <Route path={`${basePath}/followers`} component={UserFollowers}/>
      <Route path={`${basePath}/shows`} component={UserShows}/>
      <Route path={`${basePath}/series`} component={UserSeries}/>
      <Route path={`${basePath}/playlists`} component={UserPlaylists}/>
      <Route path={`${basePath}/`} component={UserOverview}/>
    </Switch>
  </Container>
)

export default enhance(UserContainer)<|endoftext|>


<commit_before>import React, {Component} from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import superagent from 'superagent';
import Well from 'react-bootstrap/lib/Well';
import Button from 'react-bootstrap/lib/Button';
import cookie from 'react-cookie';
import { Link } from 'react-router';

/* Import here only for Dispatchers */
import { getUser } from '../../redux/actions/getUserActions';
import { activateNewUser } from '../../redux/actions/activateNewUserActions';

@connect((store) => {
  return {
    getUserState: store.getUser.user,
    activateNewUserState: store.activateNewUser.userStatus,
  };
})

export default class MyProfile extends Component {
  state = {
    formStatus: 0,
    formMsg: ''
  }

  render() {
    const { getUserState } = this.props;
    const { formStatus, formMsg } = this.state;
    const styles = require('./MyProfile.scss');

    return (

Mein Profil

          <Helmet title="Mein Profil"/>

<

    );
  }

}
<commit_msg>mexiko v7.2<commit_after>import React, {Component} from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import superagent from 'superagent';
import Well from 'react-bootstrap/lib/Well';
import Button from 'react-bootstrap/lib/Button';
import cookie from 'react-cookie';
import { Link } from 'react-router';

/* Import here only for Dispatchers */
import { getUser } from '../../redux/actions/getUserActions';
import { activateNewUser } from '../../redux/actions/activateNewUserActions';

@connect((store) => {
  return {
    getUserState: store.getUser.user,
    activateNewUserState: store.activateNewUser.userStatus,
  };
})

export default class MyProfile extends Component {
  state = {
    formStatus: 0,
    formMsg: ''
  }

  render() {
    const { getUserState } = this.props;
    const { formStatus, formMsg } = this.state;
    const styles = require('./MyProfile.scss');

    return (

Mein Profil

          <Helmet title="Mein Profil"/>

    );
  }

}
<|endoftext|>


<commit_before>const childProcess = require('child_process');
const fs = require('fs');
const fetch = require('node-fetch');
const semver = require('semver');
const PromiseRouter = require('express-promise-router');

const UpdatesController = PromiseRouter();

function readVersion(packagePath) {
  return new Promise(function(resolve, reject) {
    fs.readFile(packagePath, {encoding: 'utf8'}, function(err, data) {
      if (err) {
        reject(err);
        return;
      }
      let pkg = JSON.parse(data);
      if (!semver.valid(pkg.version)) {
        reject(new Error('invalid gateway semver: ' + pkg.version));
        return;
      }
      resolve(pkg.version);
    });
  });
}

function stat(path) {
  return new Promise(function(resolve, reject) {
    fs.stat(path, function(err, stats) {
      if (err) {
        if (err.code === 'ENOENT') {
          resolve(null);
        } else {
          reject(err);
        }
      } else {
        resolve(stats);
      }
    });
  });
}

/**
 * Send the client an object describing the latest release
 */
UpdatesController.get('/latest', async function(request, response) {
  const res = await
    fetch('https://api.github.com/repos/mozilla-iot/gateway/releases');
  const releases = await res.json();
  const latestRelease = releases.filter(release => {
    return !release.prerelease && !release.draft;
  })[0];
  if (!latestRelease) {
    console.warn('No releases found');
    response.send({version: null});
    return;
  }
  let releaseVer = latestRelease.tag_name;
  response.send({version: releaseVer});
});

/**
 * Send an object describing the update status of the gateway
 */
UpdatesController.get('/status', async function(request, response) {
  // gateway, gateway_failed, gateway_old
  // oldVersion -> gateway_old's package.json version
  // if (gateway_failed.version > thisversion) {
  //  update failed, last attempt was ctime of gateway_failed
  // }
  const currentVersion = await readVersion('package.json');
  let oldStats = await stat('../gateway_old/package.json');
  let oldVersion = null;

  let failedStats = await stat('../gateway_failed/package.json');
  let failedVersion = null;

  if (oldStats) {
    oldVersion = await readVersion('../gateway_old/package.json')
  }

  if (failedStats) {
    failedVersion = await readVersion('../gateway_failed/package.json')
  }

  if (failedVersion && semver.gt(failedVersion, currentVersion)) {
    response.send({
      success: false,
      version: currentVersion,
      failedVersion: failedVersion,
      timestamp: failedStats.ctimeMs
    });
  } else {
    let timestamp = null;
    if (oldStats) {
      timestamp = oldStats.ctimeMs;
    }
    response.send({
      success: true,
      version: currentVersion,
      oldVersion: oldVersion,
      timestamp: timestamp
    });
  }
});

UpdatesController.post('/update', async function(request, response) {
  childProcess.exec('sudo systemctl start ' +
    'mozilla-iot-gateway.check-for-update.service');

  response.end();
});

module.exports = UpdatesController;
<commit_msg>Check for API rate-limiting<commit_after>const childProcess = require('child_process');
const fs = require('fs');
const fetch = require('node-fetch');
const semver = require('semver');
const PromiseRouter = require('express-promise-router');

const UpdatesController = PromiseRouter();

function readVersion(packagePath) {
  return new Promise(function(resolve, reject) {
    fs.readFile(packagePath, {encoding: 'utf8'}, function(err, data) {
      if (err) {
        reject(err);
        return;
      }
      let pkg = JSON.parse(data);
      if (!semver.valid(pkg.version)) {
        reject(new Error('invalid gateway semver: ' + pkg.version));
        return;
      }
      resolve(pkg.version);
    });
  });
}

function stat(path) {
  return new Promise(function(resolve, reject) {
    fs.stat(path, function(err, stats) {
      if (err) {
        if (err.code === 'ENOENT') {
          resolve(null);
        } else {
          reject(err);
        }
      } else {
        resolve(stats);
      }
    });
  });
}

/**
 * Send the client an object describing the latest release
 */
UpdatesController.get('/latest', async function(request, response) {
  const res = await
    fetch('https://api.github.com/repos/mozilla-iot/gateway/releases');
  const releases = await res.json();
  if (!releases || !releases.filter) {
    console.warn('API returned invalid releases, rate limit likely exceeded');
    response.send({version: null});
    return;
  }
  const latestRelease = releases.filter(release => {
    return !release.prerelease && !release.draft;
  })[0];
  if (!latestRelease) {
    console.warn('No releases found');
    response.send({version: null});
    return;
  }
  let releaseVer = latestRelease.tag_name;
  response.send({version: releaseVer});
});

/**
 * Send an object describing the update status of the gateway
 */
UpdatesController.get('/status', async function(request, response) {
  // gateway, gateway_failed, gateway_old
  // oldVersion -> gateway_old's package.json version
  // if (gateway_failed.version > thisversion) {
  //  update failed, last attempt was ctime of gateway_failed
  // }
  const currentVersion = await readVersion('package.json');
  let oldStats = await stat('../gateway_old/package.json');
  let oldVersion = null;

  let failedStats = await stat('../gateway_failed/package.json');
  let failedVersion = null;

  if (oldStats) {
    oldVersion = await readVersion('../gateway_old/package.json')
  }

  if (failedStats) {
    failedVersion = await readVersion('../gateway_failed/package.json')
  }

  if (failedVersion && semver.gt(failedVersion, currentVersion)) {
    response.send({
      success: false,
      version: currentVersion,
      failedVersion: failedVersion,
      timestamp: failedStats.ctimeMs
    });
  } else {
    let timestamp = null;
    if (oldStats) {
      timestamp = oldStats.ctimeMs;
    }
    response.send({
      success: true,
      version: currentVersion,
      oldVersion: oldVersion,
      timestamp: timestamp
    });
  }
});

UpdatesController.post('/update', async function(request, response) {
  childProcess.exec('sudo systemctl start ' +
    'mozilla-iot-gateway.check-for-update.service');

  response.end();
});

module.exports = UpdatesController;
<|endoftext|>


<commit_before>describe('ncaValidator', function () {

  var ncaValidator, $rootScope, ncaModelValidationEvents, sizeValidator,
    personRules = {
      'Person': {
        'firstName': {
          'Size': {
            'min': 0,
            'max': 10,
            'message': 'size'
          }
        }
      }
    },
    addressRules = {
      'Address': {
        'street': {
          'Required': {
            'message': 'required'
          }
        }
      }
    };

  beforeEach(module('ncaModelValidation'));

  beforeEach(inject(function (_ncaValidator_, _$rootScope_, _ncaModelValidationEvents_, _sizeValidator_) {
    ncaValidator = _ncaValidator_;
    $rootScope = _$rootScope_;
    ncaModelValidationEvents = _ncaModelValidationEvents_;
    sizeValidator = _sizeValidator_;
  }));

  describe('addValidationRules()', function () {

    it('should add initial validation rules', function () {
      // when
      ncaValidator.addValidationRules(personRules);

      // then
      expect(ncaValidator.getValidationRules()).toEqual(personRules);
    });

    it('should extend initial validation rules', function () {
      // when
      ncaValidator.addValidationRules(personRules);
      ncaValidator.addValidationRules(addressRules);

      // then
      expect(ncaValidator.getValidationRules().Person).toEqual(personRules.Person);
      expect(ncaValidator.getValidationRules().Address).toEqual(addressRules.Address);
    });

    it('should broadcast event when validation rules change', function () {
      // given
      spyOn($rootScope, '$broadcast');

      // when
      ncaValidator.addValidationRules(personRules);

      // then
      expect($rootScope.$broadcast).toHaveBeenCalledWith(ncaModelValidationEvents.rulesChanged);
    });
  });

  describe('validate()', function () {

    it('should validate with correct validator', function () {
      // given
      ncaValidator.addValidationRules(personRules);
      spyOn(sizeValidator, 'validate').andCallThrough();

      // when
      var validationResult = ncaValidator.validate('Person', 'firstName', 'Hanueli');

      // then
      expect(validationResult.valid).toBe(true);
      expect(sizeValidator.validate).toHaveBeenCalled();
    });

    it('should return invalid state and message if validation fails', function () {
      // given
      ncaValidator.addValidationRules(personRules);
      spyOn(sizeValidator, 'validate').andCallThrough();

      // when
      var validationResult = ncaValidator.validate('Person', 'firstName', 'Hanueli with a name that is too long');

      // then
      expect(sizeValidator.validate).toHaveBeenCalled();
      expect(validationResult.valid).toBe(false);
      expect(validationResult.messages[0].messageKey).toBe('size');
      expect(validationResult.messages[0].messageParams.max).toBe(10);
      expect(validationResult.messages[0].messageParams.min).toBe(0);
    });

  });

});<commit_msg>test(core): test that results are ok if multiple validations fail for one field<commit_after>describe('ncaValidator', function () {

  var ncaValidator, $rootScope, ncaModelValidationEvents, sizeValidator, requiredValidator,
    personRules = {
      'Person': {
        'firstName': {
          'Size': {
            'min': 0,
            'max': 10,
            'message': 'size'
          }
        }
      }
    },
    addressRules = {
      'Address': {
        'street': {
          'Required': {
            'message': 'required'
          }
        }
      }
    };

  beforeEach(module('ncaModelValidation'));

  beforeEach(inject(function (
      _ncaValidator_, _$rootScope_, _ncaModelValidationEvents_, _sizeValidator_, _requiredValidator_) {
    ncaValidator = _ncaValidator_;
    $rootScope = _$rootScope_;
    ncaModelValidationEvents = _ncaModelValidationEvents_;
    sizeValidator = _sizeValidator_;
    requiredValidator = _requiredValidator_;
  }));

  describe('addValidationRules()', function () {

    it('should add initial validation rules', function () {
      // when
      ncaValidator.addValidationRules(personRules);

      // then
      expect(ncaValidator.getValidationRules()).toEqual(personRules);
    });

    it('should extend initial validation rules', function () {
      // when
      ncaValidator.addValidationRules(personRules);
      ncaValidator.addValidationRules(addressRules);

      // then
      expect(ncaValidator.getValidationRules().Person).toEqual(personRules.Person);
      expect(ncaValidator.getValidationRules().Address).toEqual(addressRules.Address);
    });

    it('should broadcast event when validation rules change', function () {
      // given
      spyOn($rootScope, '$broadcast');

      // when
      ncaValidator.addValidationRules(personRules);

      // then
      expect($rootScope.$broadcast).toHaveBeenCalledWith(ncaModelValidationEvents.rulesChanged);
    });
  });

  describe('validate()', function () {

    it('should validate with correct validator', function () {
      // given
      ncaValidator.addValidationRules(personRules);
      spyOn(sizeValidator, 'validate').andCallThrough();

      // when
      var validationResult = ncaValidator.validate('Person', 'firstName', 'Hanueli');

      // then
      expect(validationResult.valid).toBe(true);
      expect(sizeValidator.validate).toHaveBeenCalled();
    });

    it('should return invalid state and message if validation fails', function () {
      // given
      ncaValidator.addValidationRules(personRules);
      spyOn(sizeValidator, 'validate').andCallThrough();

      // when
      var validationResult = ncaValidator.validate('Person', 'firstName', 'Hanueli with a name that is too long');

      // then
      expect(sizeValidator.validate).toHaveBeenCalled();
      expect(validationResult.valid).toBe(false);
      expect(validationResult.messages[0].messageKey).toBe('size');
      expect(validationResult.messages[0].messageParams.max).toBe(10);
      expect(validationResult.messages[0].messageParams.min).toBe(0);
    });

    it('should return invalid state and message if multiple validations fail', function () {
      // given
      ncaValidator.addValidationRules({
        'Person': {
          'firstName': {
            'Size': {
              'min': 2,
              'max': 10,
              'message': 'size'
            },
            'Required': {
              'message': 'required'
            }
          }
        }
      });
      spyOn(sizeValidator, 'validate').andCallThrough();
      spyOn(requiredValidator, 'validate').andCallThrough();

      // when
      var validationResult = ncaValidator.validate('Person', 'firstName', undefined);

      // then
      expect(sizeValidator.validate).toHaveBeenCalled();
      expect(requiredValidator.validate).toHaveBeenCalled();
      expect(validationResult.valid).toBe(false);
      expect(validationResult.messages[0].messageKey).toBe('size');
      expect(validationResult.messages[1].messageKey).toBe('required');
    });

  });

});<|endoftext|>


<commit_before>/*
* EventDispatcher
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

// namespace:
this.createjs = this.createjs||{};

(function() {

/**
 * Provides methods for managing prioritized queues of event listeners and dispatching events. You can either extend
 * this class or mix its methods into an existing prototype or instance by using EventDispatcher.initialize(target).
 * @class EventDispatcher
 * @constructor
 **/
var EventDispatcher = function() {
  this.initialize();
};
var p = EventDispatcher.prototype;

	/**
	 * Static initializer to mix in EventDispatcher methods.
	 * @method initialize
	 * @static
	 * @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a prototype.
	 **/
	EventDispatcher.initialize = function(target) {
		target.addEventListener = p.addEventListener;
		target.removeEventListener = p.removeEventListener;
		target.removeAllEventListeners = p.removeAllEventListeners;
		target.hasEventListener = p.hasEventListener;
		target.dispatchEvent = p.dispatchEvent;
	};

// private properties:
	/**
	 * @protected
	 * @property _listeners
	 * @type Object
	 **/
	p._listeners = null;

// constructor:
	/**
	 * Initialization method.
	 * @method initialize
	 * @protected
	 **/
	p.initialize = function() {};

// public methods:
	/**
	 * Adds the specified event listener.
	 * @method addEventListener
	 * @param {String} type The string type of the event.
	 * @param {Function} callback The function that will be called when this event is dispatched.
	 * @param {Number} priority Optional. Listeners with a higher priority will be called before those with lower priority. Default is 0.
	 **/
	p.addEventListener = function(type, callback, priority) {
		priority = priority || 0;
		var o = new Listener(callback, priority);
		var listeners = this._listeners;
		if (!listeners) { listeners = this._listeners = {}; }
		var arr = listeners[type];
		if (!arr) { arr = listeners[type] = [o]; return; }
		this.removeEventListener(type, callback);
		for (var i=0,l=arr.length; i<l && arr[i].priority >= priority; i++) {}
		arr.splice(i, 0, o);
	};

	/**
	 * Removes the specified event listener.
	 * @method removeEventListener
	 * @param {String} type The string type of the event.
	 * @param {Function} callback The listener function.
	 **/
	p.removeEventListener = function(type, callback) {
		var listeners = this._listeners;
		if (!listeners) { return; }
		var arr = listeners[type];
		if (!arr) { return; }
		for (var i=0,l=arr.length; i<l; i++) {
			var o = arr[i];
			if (o.f == callback) {
				if (l==1) { delete(listeners[type]); } // allows for faster checks.
				else { arr.splice(i,1); }
				break;
			}
		}
	};

	/**
	 * Removes all listeners for the specified type, or all listeners of all types.
	 * @method removeEventListener
	 * @param {String} type The string type of the event. If omitted, all listeners for all types will be removed.
	 **/
	p.removeAllEventListeners = function(type) {
		if (!type) { this._listeners = null; }
		else if (this._listeners) { delete(this._listeners[type]); }
	};

	/**
	 * Dispatches the specified event.
	 * @method dispatchEvent
	 * @param {Object | String} eventObj An object with a "type" property, or a string type. If a string is used, dispatchEvent will contstruct a generic event object with "type" and "params" properties.
	 * @param {Object} target Optional. The object to use as the target property of the event object. This will default to the dispatching object.
	 * @return {Boolean} Returns true if any listener returned true.
	 **/
	p.dispatchEvent = function(eventObj, target) {
		var ret, listeners = this._listeners;
		if (eventObj && listeners) {
			if (typeof eventObj == "string") { eventObj = {type:eventObj}; }
			eventObj.target = target||this;
			var arr = listeners[eventObj.type];
			if (!arr) { return !!ret; }
			for (var i=0,l=arr.length; i<l; i++) {
				var o = arr[i];
				ret = ret||o.f.apply(null, [eventObj]);
			}
		}
		return !!ret;
	};

	/**
	 * Indicates whether there is at least one listener for the specified event type or a defined callback.
	 * @method hasEventListener
	 * @param {String} type The string type of the event.
	 * @param {String} callback Optional. The name of the callback function related to this event.
	 * @return {Boolean} Returns true if there is at least one listener for the specified event.
	 **/
	p.hasEventListener = function(type) {
		var listeners = this._listeners;
		return !!(listeners && listeners[type]);
	};

	/**
	 * Returns a string representation of this object.
	 * @method toString
	 * @return {String} a string representation of the instance.
	 **/
	p.toString = function() {
		return "[EventDispatcher]";
	};

// inner classes:
	/**
	* Inner class used by the EventDispatcher class.
	 * @protected
	 * @class Listener
	 * @constructor
	 **/
	function Listener(f, priority) {
		this.f = f;
		this.priority = priority;
	}

createjs.EventDispatcher = EventDispatcher;
}());<commit_msg>Updated documentation for EventDispatcher.<commit_after>/*
* EventDispatcher
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

// namespace:
this.createjs = this.createjs||{};

(function() {

/**
 * Provides methods for managing prioritized queues of event listeners and dispatching events. You can either extend
 * this class or mix its methods into an existing prototype or instance by using EventDispatcher.initialize(target).
 * @class EventDispatcher
 * @constructor
 **/
var EventDispatcher = function() {
  this.initialize();
};
var p = EventDispatcher.prototype;

	/**
	 * Static initializer to mix in EventDispatcher methods.
	 * @method initialize
	 * @static
	 * @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a
	 * prototype.
	 **/
	EventDispatcher.initialize = function(target) {
		target.addEventListener = p.addEventListener;
		target.removeEventListener = p.removeEventListener;
		target.removeAllEventListeners = p.removeAllEventListeners;
		target.hasEventListener = p.hasEventListener;
		target.dispatchEvent = p.dispatchEvent;
	};

// private properties:
	/**
	 * @protected
	 * @property _listeners
	 * @type Object
	 **/
	p._listeners = null;

// constructor:
	/**
	 * Initialization method.
	 * @method initialize
	 * @protected
	 **/
	p.initialize = function() {};

// public methods:
	/**
	 * Adds the specified event listener.
	 * @method addEventListener
	 * @param {String} type The string type of the event.
	 * @param {Function} callback The function that will be called when this event is dispatched.
	 * @param {Number} [priority=0] Listeners with a higher priority will be called before those with lower priority.
	 **/
	p.addEventListener = function(type, callback, priority) {
		priority = priority || 0;
		var o = new Listener(callback, priority);
		var listeners = this._listeners;
		if (!listeners) { listeners = this._listeners = {}; }
		var arr = listeners[type];
		if (!arr) { arr = listeners[type] = [o]; return; }
		this.removeEventListener(type, callback);
		for (var i=0,l=arr.length; i<l && arr[i].priority >= priority; i++) {}
		arr.splice(i, 0, o);
	};

	/**
	 * Removes the specified event listener.
	 * @method removeEventListener
	 * @param {String} type The string type of the event.
	 * @param {Function} callback The listener function.
	 **/
	p.removeEventListener = function(type, callback) {
		var listeners = this._listeners;
		if (!listeners) { return; }
		var arr = listeners[type];
		if (!arr) { return; }
		for (var i=0,l=arr.length; i<l; i++) {
			var o = arr[i];
			if (o.f == callback) {
				if (l==1) { delete(listeners[type]); } // allows for faster checks.
				else { arr.splice(i,1); }
				break;
			}
		}
	};

	/**
	 * Removes all listeners for the specified type, or all listeners of all types.
	 * @method removeEventListener
	 * @param {String} [type] The string type of the event. If omitted, all listeners for all types will be removed.
	 **/
	p.removeAllEventListeners = function(type) {
		if (!type) { this._listeners = null; }
		else if (this._listeners) { delete(this._listeners[type]); }
	};

	/**
	 * Dispatches the specified event.
	 * @method dispatchEvent
	 * @param {Object | String} eventObj An object with a "type" property, or a string type. If a string is used,
	 * dispatchEvent will contstruct a generic event object with "type" and "params" properties.
	 * @param {Object} [target] The object to use as the target property of the event object. This will default to the
	 * dispatching object.
	 * @return {Boolean} Returns true if any listener returned true.
	 **/
	p.dispatchEvent = function(eventObj, target) {
		var ret, listeners = this._listeners;
		if (eventObj && listeners) {
			if (typeof eventObj == "string") { eventObj = {type:eventObj}; }
			eventObj.target = target||this;
			var arr = listeners[eventObj.type];
			if (!arr) { return !!ret; }
			for (var i=0,l=arr.length; i<l; i++) {
				var o = arr[i];
				ret = ret||o.f.apply(null, [eventObj]);
			}
		}
		return !!ret;
	};

	/**
	 * Indicates whether there is at least one listener for the specified event type or a defined callback.
	 * @method hasEventListener
	 * @param {String} type The string type of the event.
	 * @return {Boolean} Returns true if there is at least one listener for the specified event.
	 **/
	p.hasEventListener = function(type) {
		var listeners = this._listeners;
		return !!(listeners && listeners[type]);
	};

	/**
	 * @method toString
	 * @return {String} a string representation of the instance.
	 **/
	p.toString = function() {
		return "[EventDispatcher]";
	};

// inner classes:
	/**
	 * Inner class used by the EventDispatcher class.
	 * @protected
	 * @class Listener
	 * @constructor
	 **/
	function Listener(f, priority) {
		this.f = f;
		this.priority = priority;
	}

createjs.EventDispatcher = EventDispatcher;
}());<|endoftext|>