<commit_before>"use strict";

meta.class("meta.CanvasRenderer", "meta.Renderer",
{
	init: function()
	{
		this._super();

		this.ctx = meta.engine.canvas.getContext("2d");
		meta.engine.ctx = this.ctx;
	},

	render: function(tDelta, alpha)
	{
		var numEntities = this.entitiesAnim.length;
		for(var i = 0; i < numEntities; i++) {
			this.entitiesAnim[i].update(tDelta);
		}

		if(!this.needRender) { return; }

		this.clear();

		this.ctx.save();

		var zoom = this.camera._zoom;
		this.ctx.setTransform(zoom, 0, 0, zoom, -this.cameraVolume.x * zoom | 0, -this.cameraVolume.y * zoom | 0);

		numEntities = this.entities.length;
		for(i = 0; i < numEntities; i++) {
			this.drawEntity(this.entities[i]);
		}

		var numFuncs = this._renderFuncs.length;
		for(i = 0; i < numFuncs; i++) {
			this._renderFuncs[i].render(tDelta);
		}

		var entity = null;

		/* Debug */
		if(this.meta.cache.debug || this.numDebug > 0)
		{
			this.ctx.save();
			this.ctx.lineWidth = 2;
			this.ctx.strokeStyle = "red";
			this.ctx.fillStyle = "red";

			for(i = 0; i < numEntities; i++)
			{
				entity = this.entities[i];
				if(entity.flags & this.entityFlags.DEBUG || this.meta.cache.debug)
				{
					if(entity._static)
					{
						var zoom = this.camera._zoom;

						if(entity._debugger) {
							this.ctx.setTransform(1, 0, 0, 1, 0, 0);
						}
						else {
							this.ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
						}

						this.drawVolume(entity);
						this.ctx.setTransform(zoom, 0, 0, zoom, -this.cameraVolume.x * zoom | 0, -this.cameraVolume.y * zoom | 0);
					}
					else {
						this.drawVolume(entity);
					}
				}
			}

			this.ctx.restore();
		}

		this.needRender = false;

		this.ctx.restore();
	},

	clear: function()
	{
		if(this._transparent) {
			this.ctx.clearRect(0, 0, this.engine.width, this.engine.height);
		}
		else {
			this.ctx.fillStyle = this._bgColor;
			this.ctx.fillRect(0, 0, this.engine.width, this.engine.height);
		}
	},

	drawEntity: function(entity)
	{
		if(!entity._visible) { return; }

		if(entity._static)
		{
			var zoom = this.camera._zoom;

			if(entity._debugger) {
				this.ctx.setTransform(1, 0, 0, 1, 0, 0);
			}
			else {
				this.ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
			}

			if(entity.draw) {
				this.ctx.save();
				entity.draw(this.ctx);
				this.ctx.restore();
			}
			else {
				this._drawEntity(entity);
			}

			this.ctx.setTransform(zoom, 0, 0, zoom, -this.cameraVolume.x * zoom | 0, -this.cameraVolume.y * zoom | 0);
		}
		else
		{
			if(entity.draw) {
				this.ctx.save();
				entity.draw(this.ctx);
				this.ctx.restore();
			}
			else {
				this._drawEntity(entity);
			}
		}
	},

	_drawEntity: function(entity)
	{
		var texture = entity._texture;
		if(!texture) { return; }

		var volume = entity.volume;
		var anim = entity.anim;

		if(entity.clipVolume)
		{
			this.ctx.save();

			this.ctx.beginPath();

			if(entity.flags & this.entityFlags.CLIP_BOUNDS) {
				this.ctx.rect(entity.volume.minX | 0, entity.volume.minY | 0,
					entity.clipVolume.width, entity.clipVolume.height);
			}
			else {
				this.ctx.rect(entity.clipVolume.minX | 0, entity.clipVolume.minY | 0,
					entity.clipVolume.width, entity.clipVolume.height);
			}

			this.ctx.closePath();
			this.ctx.clip();
		}

		if(!volume.__transformed)
		{
			if(texture.frames > 1) {
				texture.drawFrame(this.ctx, volume.minX | 0, volume.minY | 0, anim._frame);
			}
			else {
				this.ctx.drawImage(texture.canvas, volume.minX | 0, volume.minY | 0);
			}

			this.ctx.restore();
		}
		else
		{
			this.ctx.globalAlpha = entity.totalAlpha;

			this.ctx.transform(
				volume.m11, volume.m12,
				volume.m21, volume.m22,
				volume.x | 0, volume.y | 0);

			if(texture.frames > 1) {
				texture.drawFrame(this.ctx, -volume.initPivotPosX, -volume.initPivotPosY, anim._frame);
			}
			else
			{
				if(texture.fromAtlas)
				{
					this.ctx.drawImage(texture.ptr.canvas,
						texture.x, texture.y, texture.fullWidth, texture.fullHeight,
						-volume.initPivotPosX, -volume.initPivotPosY, texture.fullWidth, texture.fullHeight);
				}
				else {
					this.ctx.drawImage(texture.canvas, -volume.initPivotPosX, -volume.initPivotPosY);
				}
			}

			this.ctx.globalAlpha = 1.0;

			var zoom = this.camera._zoom;
			this.ctx.setTransform(zoom, 0, 0, zoom, -this.camera.volume.x * zoom | 0, -this.camera.volume.y * zoom | 0);
		}

		if(entity.clipVolume) {
			this.ctx.restore();
		}
	},

	drawVolume: function(entity)
	{
		if(!entity._visible) { return; }
		if(entity._debugger) { return; }

		var volume = entity.volume;

		if(volume.__type === 0) {
			this._drawVolume(volume);
		}
		else
		{
			this.ctx.save();

			this.ctx.translate(volume.x | 0, volume.y | 0);
			this.ctx.rotate(entity.volume.angle);
			this.ctx.translate(-volume.x | 0, -volume.y | 0);

			this._drawVolume(volume);

			this.ctx.restore();
		}
	},

	_drawVolume: function(volume)
	{
		var minX = Math.floor(volume.minX);
		var minY = Math.floor(volume.minY);
		var maxX = Math.ceil(volume.maxX);
		var maxY = Math.ceil(volume.maxY - 1);

		this.ctx.beginPath();
		this.ctx.moveTo(minX, minY);
		this.ctx.lineTo(maxX, minY);
		this.ctx.lineTo(maxX, maxY);
		this.ctx.lineTo(minX, maxY);
		this.ctx.lineTo(minX, minY - 1);
		this.ctx.stroke();

		this.ctx.fillRect(volume.x - 3, volume.y - 3, 6, 6);
	},

	updateBgColor: function() {}
});
<commit_msg>Fixed rendering bug.<commit_after>"use strict";

meta.class("meta.CanvasRenderer", "meta.Renderer",
{
	init: function()
	{
		this._super();

		this.ctx = meta.engine.canvas.getContext("2d");
		meta.engine.ctx = this.ctx;
	},

	render: function(tDelta, alpha)
	{
		var numEntities = this.entitiesAnim.length;
		for(var i = 0; i < numEntities; i++) {
			this.entitiesAnim[i].update(tDelta);
		}

		if(!this.needRender) { return; }

		this.clear();

		this.ctx.save();

		var zoom = this.camera._zoom;
		this.ctx.setTransform(zoom, 0, 0, zoom, -this.cameraVolume.x * zoom | 0, -this.cameraVolume.y * zoom | 0);

		numEntities = this.entities.length;
		for(i = 0; i < numEntities; i++) {
			this.drawEntity(this.entities[i]);
		}

		var numFuncs = this._renderFuncs.length;
		for(i = 0; i < numFuncs; i++) {
			this._renderFuncs[i].render(tDelta);
		}

		var entity = null;

		/* Debug */
		if(this.meta.cache.debug || this.numDebug > 0)
		{
			this.ctx.save();
			this.ctx.lineWidth = 2;
			this.ctx.strokeStyle = "red";
			this.ctx.fillStyle = "red";

			for(i = 0; i < numEntities; i++)
			{
				entity = this.entities[i];
				if(entity.flags & this.entityFlags.DEBUG || this.meta.cache.debug)
				{
					if(entity._static)
					{
						var zoom = this.camera._zoom;

						if(entity._debugger) {
							this.ctx.setTransform(1, 0, 0, 1, 0, 0);
						}
						else {
							this.ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
						}

						this.drawVolume(entity);
						this.ctx.setTransform(zoom, 0, 0, zoom, -this.cameraVolume.x * zoom | 0, -this.cameraVolume.y * zoom | 0);
					}
					else {
						this.drawVolume(entity);
					}
				}
			}

			this.ctx.restore();
		}

		this.needRender = false;

		this.ctx.restore();
	},

	clear: function()
	{
		if(this._transparent) {
			this.ctx.clearRect(0, 0, this.engine.width, this.engine.height);
		}
		else {
			this.ctx.fillStyle = this._bgColor;
			this.ctx.fillRect(0, 0, this.engine.width, this.engine.height);
		}
	},

	drawEntity: function(entity)
	{
		if(!entity._visible) { return; }

		if(entity._static)
		{
			var zoom = this.camera._zoom;

			if(entity._debugger) {
				this.ctx.setTransform(1, 0, 0, 1, 0, 0);
			}
			else {
				this.ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
			}

			if(entity.draw) {
				this.ctx.save();
				entity.draw(this.ctx);
				this.ctx.restore();
			}
			else {
				this._drawEntity(entity);
			}

			this.ctx.setTransform(zoom, 0, 0, zoom, -this.cameraVolume.x * zoom | 0, -this.cameraVolume.y * zoom | 0);
		}
		else
		{
			if(entity.draw) {
				this.ctx.save();
				entity.draw(this.ctx);
				this.ctx.restore();
			}
			else {
				this._drawEntity(entity);
			}
		}
	},

	_drawEntity: function(entity)
	{
		var texture = entity._texture;
		if(!texture) { return; }

		var volume = entity.volume;
		var anim = entity.anim;

		if(entity.clipVolume)
		{
			this.ctx.save();

			this.ctx.beginPath();

			if(entity.flags & this.entityFlags.CLIP_BOUNDS) {
				this.ctx.rect(entity.volume.minX | 0, entity.volume.minY | 0,
					entity.clipVolume.width, entity.clipVolume.height);
			}
			else {
				this.ctx.rect(entity.clipVolume.minX | 0, entity.clipVolume.minY | 0,
					entity.clipVolume.width, entity.clipVolume.height);
			}

			this.ctx.closePath();
			this.ctx.clip();
		}

		if(!volume.__transformed)
		{
			if(texture.frames > 1) {
				texture.drawFrame(this.ctx, volume.minX | 0, volume.minY | 0, anim._frame);
			}
			else {
				this.ctx.drawImage(texture.canvas, volume.minX | 0, volume.minY | 0);
			}
		}
		else
		{
			this.ctx.globalAlpha = entity.totalAlpha;

			this.ctx.transform(
				volume.m11, volume.m12,
				volume.m21, volume.m22,
				volume.x | 0, volume.y | 0);

			if(texture.frames > 1) {
				texture.drawFrame(this.ctx, -volume.initPivotPosX, -volume.initPivotPosY, anim._frame);
			}
			else
			{
				if(texture.fromAtlas)
				{
					this.ctx.drawImage(texture.ptr.canvas,
						texture.x, texture.y, texture.fullWidth, texture.fullHeight,
						-volume.initPivotPosX, -volume.initPivotPosY, texture.fullWidth, texture.fullHeight);
				}
				else {
					this.ctx.drawImage(texture.canvas, -volume.initPivotPosX, -volume.initPivotPosY);
				}
			}

			this.ctx.globalAlpha = 1.0;

			var zoom = this.camera._zoom;
			this.ctx.setTransform(zoom, 0, 0, zoom, -this.camera.volume.x * zoom | 0, -this.camera.volume.y * zoom | 0);
		}

		if(entity.clipVolume) {
			this.ctx.restore();
		}
	},

	drawVolume: function(entity)
	{
		if(!entity._visible) { return; }
		if(entity._debugger) { return; }

		var volume = entity.volume;

		if(volume.__type === 0) {
			this._drawVolume(volume);
		}
		else
		{
			this.ctx.save();

			this.ctx.translate(volume.x | 0, volume.y | 0);
			this.ctx.rotate(entity.volume.angle);
			this.ctx.translate(-volume.x | 0, -volume.y | 0);

			this._drawVolume(volume);

			this.ctx.restore();
		}
	},

	_drawVolume: function(volume)
	{
		var minX = Math.floor(volume.minX);
		var minY = Math.floor(volume.minY);
		var maxX = Math.ceil(volume.maxX);
		var maxY = Math.ceil(volume.maxY - 1);

		this.ctx.beginPath();
		this.ctx.moveTo(minX, minY);
		this.ctx.lineTo(maxX, minY);
		this.ctx.lineTo(maxX, maxY);
		this.ctx.lineTo(minX, maxY);
		this.ctx.lineTo(minX, minY - 1);
		this.ctx.stroke();

		this.ctx.fillRect(volume.x - 3, volume.y - 3, 6, 6);
	},

	updateBgColor: function() {}
});
<|endoftext|>


<commit_before>// @flow

class Board {

    grid: Pin[][][];
    pins: BoardPins;

    /**
     *
     */
    constructor() {
        /**
         * Grids which holds the pins of components
         * @type {Pin[][][]}
         */
        this.grid = [];
        /**
         * A dictionary, position => pins
         * @type {BoardPins}
         */
        this.pins = {};
    }

    /**
     *
     */
    add(component: Component, pinPositions: number[][]): void {
        for(let index in component.pins) {
            let pos = pinPositions[index];

            if(pos == undefined) {
                throw new Error('Missing pin position');
            }

            component.pins[index].position = pos;
            if(this.grid[pos[0]] === undefined) {
                this.grid[pos[0]] = [];
            }
            this.grid[pos[0]][pos[1]] = (this.grid[pos[0]][pos[1]] || []).concat(component.pins[index]);
            this.pins[pos.toString()] = this.grid[pos[0]][pos[1]];
        }
    }

    /**
     *
     */
    remove(component: Component): void {
        let pinPositions = component.pins.map(pin => {
            let pos = pin.position.slice();
            pin.position = null;
            return pos;
        });

        pinPositions.forEach(pos => {
            let pinIndex = -1,
                pinArray = this.grid[pos[0]][pos[1]];
            for(let index in pinArray) {
                if(pinArray[index].parent.id === component.id) {
                    pinIndex = index;
                    break;
                }
            }
            pinArray.splice(pinIndex, 1);
        });
    }
}

module.exports = Board;<commit_msg>Added ESDoc comments to Board class.<commit_after>// @flow

/**
 * This class holds the pins of the components.
 */
class Board {

    grid: Pin[][][];
    pins: BoardPins;

    /**
     * Initialises the fields
     */
    constructor() {
        /**
         * Grids which holds the pins of components
         * @type {Pin[][][]}
         */
        this.grid = [];
        /**
         * A dictionary, position => pins
         * @type {BoardPins}
         */
        this.pins = {};
    }

    /**
     * Adds a component to the board and adds the position to
     * the respective component pins
     * @param {Component} component
     * @param {number[][]} pinPositions
     */
    add(component: Component, pinPositions: number[][]): void {
        for(let index in component.pins) {
            let pos = pinPositions[index];

            if(pos == undefined) {
                throw new Error('Missing pin position');
            }

            component.pins[index].position = pos;
            if(this.grid[pos[0]] === undefined) {
                this.grid[pos[0]] = [];
            }
            this.grid[pos[0]][pos[1]] = (this.grid[pos[0]][pos[1]] || []).concat(component.pins[index]);
            this.pins[pos.toString()] = this.grid[pos[0]][pos[1]];
        }
    }

    /**
     * Removes the given component from the board and also
     * removes the position from the respective pins
     * @param {Component} component
     */
    remove(component: Component): void {
        let pinPositions = component.pins.map(pin => {
            let pos = pin.position.slice();
            pin.position = null;
            return pos;
        });

        pinPositions.forEach(pos => {
            let pinIndex = -1,
                pinArray = this.grid[pos[0]][pos[1]];
            for(let index in pinArray) {
                if(pinArray[index].parent.id === component.id) {
                    pinIndex = index;
                    break;
                }
            }
            pinArray.splice(pinIndex, 1);
        });
    }
}

module.exports = Board;<|endoftext|>


<commit_before>import MapFacade from '../map/MapFacade';
import OrthogonalFormat from '../map/format/orthogonal/OrthogonalFormat';

import React from 'react';
import Relay from 'react-relay';

import ChangeTileTypeMutation from '../mutations/ChangeTileTypeMutation';

class Map extends React.Component {
    constructor(){
        super();

        this._oldProps = {
            tileset: {
                availableTileTypes: 0,
                rows: []
            }
        };

        this._mapFacade = null;
    }
    render() {
        const component = this;

        if(this._mapFacade !== null){
            this._update();
        }

        return (
            <canvas className="map" ref="mapRef"></canvas>
        );
    }
    _update(){
        this._mapFacade.setTileTypes(this.props.tileset.availableTileTypes);
        this._updateTiles();
    }
    _updateTiles(){
        const component = this;

        this._updateSize();

        const rows = this.props.tileset.rows;
        rows.forEach(function(currRow, rowIdx){
            component._updateRow.apply(component, [currRow, rowIdx]);
        });
    }
    _updateSize(){
        const rows = this.props.tileset.rows;

        const numRows = rows.length;
        const numCols = (numRows === 0)?0:rows[0].tiles.length;

        this._mapFacade._setSize(numCols, numRows);
    }
    _updateRow(row, rowIdx){
        const component = this;

        const mapFacade = this._mapFacade;
        const tiles = row.tiles;

        tiles.forEach(function(tile, tileIdx){
            mapFacade.setTileType(tileIdx, rowIdx, tile.type);
        });
    }
    _handleTileClicked(numCol, numRow){
        const tile = this.props.tileset.rows[numRow].tiles[numCol];
        const newTileType = (tile.type + 1) % this.props.tileset.availableTileTypes;

        this.props.relay.commitUpdate(
            new ChangeTileTypeMutation({
                tile: tile,
                tileType: newTileType
            })
        );
    }
    componentDidMount(){
        const mapRef = this.refs.mapRef;

        this._initMap(mapRef);
        this._update();
    }
    _initMap(canvas){
        const mapFacade = new MapFacade(canvas, new OrthogonalFormat());
        mapFacade.start();
        this._mapFacade = mapFacade;

        // Watch for browser/canvas resize events
        window.addEventListener('resize', function () {
            mapFacade.resize();
        });

        this._mapFacade.setTileClickedListener(this._handleTileClicked.bind(this));
    }
    componentWillUnmount(){
        const engine = this._engine;
        engine.dispose();
    }
    shouldComponentUpdate(nextProps, nextState) {
        this._oldProps = nextProps;

        return true;
    }
}

export default Relay.createContainer(Map, {
    fragments: {
        tileset: () => Relay.QL`
            fragment on Tileset {
                rows{
                    id,
                    tiles {
                        id,
                        type,
                        ${ChangeTileTypeMutation.getFragment('tile')}
                    },
                },
                availableTileTypes
            }
        `
    }
});
<commit_msg>Focus on the map after mounting it<commit_after>import MapFacade from '../map/MapFacade';
import OrthogonalFormat from '../map/format/orthogonal/OrthogonalFormat';

import React from 'react';
import Relay from 'react-relay';

import ChangeTileTypeMutation from '../mutations/ChangeTileTypeMutation';

class Map extends React.Component {
    constructor(){
        super();

        this._oldProps = {
            tileset: {
                availableTileTypes: 0,
                rows: []
            }
        };

        this._mapFacade = null;
    }
    render() {
        const component = this;

        if(this._mapFacade !== null){
            this._update();
        }

        return (
            <canvas className="map" ref="mapRef"></canvas>
        );
    }
    _update(){
        this._mapFacade.setTileTypes(this.props.tileset.availableTileTypes);
        this._updateTiles();
    }
    _updateTiles(){
        const component = this;

        this._updateSize();

        const rows = this.props.tileset.rows;
        rows.forEach(function(currRow, rowIdx){
            component._updateRow.apply(component, [currRow, rowIdx]);
        });
    }
    _updateSize(){
        const rows = this.props.tileset.rows;

        const numRows = rows.length;
        const numCols = (numRows === 0)?0:rows[0].tiles.length;

        this._mapFacade._setSize(numCols, numRows);
    }
    _updateRow(row, rowIdx){
        const component = this;

        const mapFacade = this._mapFacade;
        const tiles = row.tiles;

        tiles.forEach(function(tile, tileIdx){
            mapFacade.setTileType(tileIdx, rowIdx, tile.type);
        });
    }
    _handleTileClicked(numCol, numRow){
        const tile = this.props.tileset.rows[numRow].tiles[numCol];
        const newTileType = (tile.type + 1) % this.props.tileset.availableTileTypes;

        this.props.relay.commitUpdate(
            new ChangeTileTypeMutation({
                tile: tile,
                tileType: newTileType
            })
        );
    }
    componentDidMount(){
        const mapRef = this.refs.mapRef;

        this._initMap(mapRef);
        this._update();
        mapRef.focus();
    }
    _initMap(canvas){
        const mapFacade = new MapFacade(canvas, new OrthogonalFormat());
        mapFacade.start();
        this._mapFacade = mapFacade;

        // Watch for browser/canvas resize events
        window.addEventListener('resize', function () {
            mapFacade.resize();
        });

        this._mapFacade.setTileClickedListener(this._handleTileClicked.bind(this));
    }
    componentWillUnmount(){
        const engine = this._engine;
        engine.dispose();
    }
    shouldComponentUpdate(nextProps, nextState) {
        this._oldProps = nextProps;

        return true;
    }
}

export default Relay.createContainer(Map, {
    fragments: {
        tileset: () => Relay.QL`
            fragment on Tileset {
                rows{
                    id,
                    tiles {
                        id,
                        type,
                        ${ChangeTileTypeMutation.getFragment('tile')}
                    },
                },
                availableTileTypes
            }
        `
    }
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import pureRender from 'pure-render-decorator'
import { connect } from 'react-redux'
import { selectReport, fetchDataIfNeeded, invalidateData, hideCompareView,
         showSelectedQuestions, showAllQuestions, setNowShowing, setAnonymous } from '../actions'
import { Modal } from 'react-bootstrap'
import Button from '../components/button'
import CompareView from '../components/compare-view'
import Report from '../components/report'
import DataFetchError from '../components/data-fetch-error'
import LoadingIcon from '../components/loading-icon'
import ccLogoSrc from '../../img/cc-logo.png'
// (...)Data functions accept some state and return data in a form suitable for 'dumb' components.
import reportData from '../core/report-data'
import compareViewData from '../core/compare-view-data'

import '../../css/app.less'

@pureRender
class App extends Component {
  constructor(props) {
    super(props)
    this.handleRefreshClick = this.handleRefreshClick.bind(this)
  }

  componentDidMount() {
    const { fetchDataIfNeeded } = this.props
    fetchDataIfNeeded()
  }

  handleRefreshClick(e) {
    e.preventDefault()

    const { invalidateData, fetchDataIfNeeded } = this.props
    invalidateData()
    fetchDataIfNeeded()
  }

  renderStatusBar() {
    const { isFetching, lastUpdated } = this.props
    return (

        {lastUpdated && Last updated at {new Date(lastUpdated).toLocaleTimeString()} }
        {<Button onClick={this.handleRefreshClick} disabled={isFetching}>Refresh</Button>}

    )
  }

  renderReport() {
    const { report, showSelectedQuestions, showAllQuestions, setNowShowing, setAnonymous } = this.props
    return <Report report={report}
                   howSelectedQuestions={showSelectedQuestions}
                   showAllQuestions={showAllQuestions}
                   setNowShowing={setNowShowing} setAnonymous={setAnonymous}/>
  }

  renderCompareView() {
    const { compareViewAnswers, hideCompareView } = this.props
    return (
      <Modal show={compareViewAnswers && compareViewAnswers.size > 0} bsStyle='compare-view' onHide={hideCompareView}>
        <Modal.Body>
          {compareViewAnswers && <CompareView answers={compareViewAnswers}/>}
        </Modal.Body>
      </Modal>
    )
  }

  render() {
    const { report, error, isFetching } = this.props
    return (

            {this.renderStatusBar()}

          {report && this.renderReport()}
          {error && <DataFetchError error={error}/>}
          {this.renderCompareView()}

        {isFetching && <LoadingIcon/>}

    )
  }
}

function mapStateToProps(state) {
  const data = state.get('data')
  const error = data.get('error')
  const reportState = state.get('report')
  const compareViewAnswers = reportState && reportState.get('compareViewAnswers')
  const dataDownloaded = !error && !!data.get('lastUpdated')
  return {
    isFetching: data.get('isFetching'),
    lastUpdated: data.get('lastUpdated'),
    error: error,
    report: dataDownloaded && reportData(reportState),
    compareViewAnswers: compareViewAnswers && compareViewData(reportState)
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    fetchDataIfNeeded: () => dispatch(fetchDataIfNeeded()),
    invalidateData: () => dispatch(invalidateData()),
    showSelectedQuestions: () => dispatch(showSelectedQuestions()),
    showAllQuestions: () => dispatch(showAllQuestions()),
    setNowShowing: value => dispatch(setNowShowing(value)),
    setAnonymous: value => dispatch(setAnonymous(value)),
    hideCompareView: () => dispatch(hideCompareView())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
<commit_msg>Fix regression 'show selected' from individual_reports work.<commit_after>import React, { Component } from 'react'
import pureRender from 'pure-render-decorator'
import { connect } from 'react-redux'
import { selectReport, fetchDataIfNeeded, invalidateData, hideCompareView,
         showSelectedQuestions, showAllQuestions, setNowShowing, setAnonymous } from '../actions'
import { Modal } from 'react-bootstrap'
import Button from '../components/button'
import CompareView from '../components/compare-view'
import Report from '../components/report'
import DataFetchError from '../components/data-fetch-error'
import LoadingIcon from '../components/loading-icon'
import ccLogoSrc from '../../img/cc-logo.png'
// (...)Data functions accept some state and return data in a form suitable for 'dumb' components.
import reportData from '../core/report-data'
import compareViewData from '../core/compare-view-data'

import '../../css/app.less'

@pureRender
class App extends Component {
  constructor(props) {
    super(props)
    this.handleRefreshClick = this.handleRefreshClick.bind(this)
  }

  componentDidMount() {
    const { fetchDataIfNeeded } = this.props
    fetchDataIfNeeded()
  }

  handleRefreshClick(e) {
    e.preventDefault()

    const { invalidateData, fetchDataIfNeeded } = this.props
    invalidateData()
    fetchDataIfNeeded()
  }

  renderStatusBar() {
    const { isFetching, lastUpdated } = this.props
    return (

        {lastUpdated && Last updated at {new Date(lastUpdated).toLocaleTimeString()} }
        {<Button onClick={this.handleRefreshClick} disabled={isFetching}>Refresh</Button>}

    )
  }

  renderReport() {
    const { report, showSelectedQuestions, showAllQuestions, setNowShowing, setAnonymous } = this.props
    return <Report report={report}
                   showSelectedQuestions={showSelectedQuestions}
                   showAllQuestions={showAllQuestions}
                   setNowShowing={setNowShowing} setAnonymous={setAnonymous}/>
  }

  renderCompareView() {
    const { compareViewAnswers, hideCompareView } = this.props
    return (
      <Modal show={compareViewAnswers && compareViewAnswers.size > 0} bsStyle='compare-view' onHide={hideCompareView}>
        <Modal.Body>
          {compareViewAnswers && <CompareView answers={compareViewAnswers}/>}
        </Modal.Body>
      </Modal>
    )
  }

  render() {
    const { report, error, isFetching } = this.props
    return (

            {this.renderStatusBar()}

          {report && this.renderReport()}
          {error && <DataFetchError error={error}/>}
          {this.renderCompareView()}

        {isFetching && <LoadingIcon/>}

    )
  }
}

function mapStateToProps(state) {
  const data = state.get('data')
  const error = data.get('error')
  const reportState = state.get('report')
  const compareViewAnswers = reportState && reportState.get('compareViewAnswers')
  const dataDownloaded = !error && !!data.get('lastUpdated')
  return {
    isFetching: data.get('isFetching'),
    lastUpdated: data.get('lastUpdated'),
    error: error,
    report: dataDownloaded && reportData(reportState),
    compareViewAnswers: compareViewAnswers && compareViewData(reportState)
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    fetchDataIfNeeded: () => dispatch(fetchDataIfNeeded()),
    invalidateData: () => dispatch(invalidateData()),
    showSelectedQuestions: () => dispatch(showSelectedQuestions()),
    showAllQuestions: () => dispatch(showAllQuestions()),
    setNowShowing: value => dispatch(setNowShowing(value)),
    setAnonymous: value => dispatch(setAnonymous(value)),
    hideCompareView: () => dispatch(hideCompareView())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
<|endoftext|>


<commit_before>function repeatContentOfBrackets(str) {
    if (str === undefined) return;
    var reg = /^([a-zA-Z]*)(\d+\[.+\])*[a-zA-Z]*$/;//嵌套模式的合法性,递归时检查
    var regModel = str.match(reg);
    if (!regModel) return; //输入不合法直接返回
    if (!regModel[2]) return regModel[1];
    var result = regModel[1];
    //处理并列模式,eg:3[ad]4[xs]
    var models = splitModels(str);
    var modelsLen = models.length;
    if (modelsLen === 1) {
        var model = models[0];
        if (!model.hasNest) {
            return result + model.content.repeat(model.repeation);
        }
        if (!model.content.match(reg)) return;// //如果嵌套内容不合法,则直接返回
        return result + repeatContentOfBrackets(model.content).repeat(model.repeation);
    }else {
        for (var i = 0; i < modelsLen; i++) {
            if (!repeatContentOfBrackets(models[i].modelStr)) {//某些并列模式中可能存在嵌套结构,如果内层返回undefined,则整个会返回undefined
                return;
            }
            result += repeatContentOfBrackets(models[i].modelStr);
        }
        return result;
    }

}

function splitModels(str) {
    var result = [];
    while (str) {
        if (!popModel(str)) {
            break;
        }
        var matched = popModel(str);
        result.push(matched);
        str = matched.lastStr;
    }
    return result;
}

function popModel(str) {
    var nestCount = 0 ,i;
    var begin = str.indexOf('[');
    var hasNest = false;
    if (begin === -1) {
        return {
            "modelStr": str,
            "lastStr": '',
            "hasNest": hasNest
        };
    }
    var arr = str.split('');
    var len = str.length;
    for (i = begin + 1; i < len; i++) {
        if (arr[i] === ']' && nestCount === 0) {
            break;
        }else if (arr[i] === ']') {
            nestCount -- ;
        } else if (arr[i] === '[') {
            hasNest = true;
            nestCount ++ ;
        }
    }
    var content = str.substring(begin + 1,i);
    var modelStr = str.substring(0,i+1);
    var repeation = modelStr.match(/(\d+)/)[1];
    if (i < len) {
        return {
            "modelStr": modelStr,
            "content": content,
            "repeation": repeation,
            "lastStr": str.substring(i+1),
            "hasNest": hasNest
        };
    }
}
module.exports = repeatContentOfBrackets;

repeatContentOfBrackets('3xy');
repeatContentOfBrackets('xy2');
repeatContentOfBrackets('xy');
repeatContentOfBrackets('abc3[a]');
repeatContentOfBrackets('3[abc]');
repeatContentOfBrackets('4[abc]ty');//abcabcabcabcty
repeatContentOfBrackets('3[2[ad]3[pf]]xyz');//adadpfpfpfadadpfpfpfadadpfpfpfxyz
<commit_msg>delete test<commit_after>function repeatContentOfBrackets(str) {
    if (str === undefined) return;
    var reg = /^([a-zA-Z]*)(\d+\[.+\])*[a-zA-Z]*$/;//嵌套模式的合法性,递归时检查
    var regModel = str.match(reg);
    if (!regModel) return; //输入不合法直接返回
    if (!regModel[2]) return regModel[1];
    var result = regModel[1];
    //处理并列模式,eg:3[ad]4[xs]
    var models = splitModels(str);
    var modelsLen = models.length;
    if (modelsLen === 1) {
        var model = models[0];
        if (!model.hasNest) {
            return result + model.content.repeat(model.repeation);
        }
        if (!model.content.match(reg)) return;// //如果嵌套内容不合法,则直接返回
        return result + repeatContentOfBrackets(model.content).repeat(model.repeation);
    }else {
        for (var i = 0; i < modelsLen; i++) {
            if (!repeatContentOfBrackets(models[i].modelStr)) {//某些并列模式中可能存在嵌套结构,如果内层返回undefined,则整个会返回undefined
                return;
            }
            result += repeatContentOfBrackets(models[i].modelStr);
        }
        return result;
    }

}

function splitModels(str) {
    var result = [];
    while (str) {
        if (!popModel(str)) {
            break;
        }
        var matched = popModel(str);
        result.push(matched);
        str = matched.lastStr;
    }
    return result;
}

function popModel(str) {
    var nestCount = 0 ,i;
    var begin = str.indexOf('[');
    var hasNest = false;
    if (begin === -1) {
        return {
            "modelStr": str,
            "lastStr": '',
            "hasNest": hasNest
        };
    }
    var arr = str.split('');
    var len = str.length;
    for (i = begin + 1; i < len; i++) {
        if (arr[i] === ']' && nestCount === 0) {
            break;
        }else if (arr[i] === ']') {
            nestCount -- ;
        } else if (arr[i] === '[') {
            hasNest = true;
            nestCount ++ ;
        }
    }
    var content = str.substring(begin + 1,i);
    var modelStr = str.substring(0,i+1);
    var repeation = modelStr.match(/(\d+)/)[1];
    if (i < len) {
        return {
            "modelStr": modelStr,
            "content": content,
            "repeation": repeation,
            "lastStr": str.substring(i+1),
            "hasNest": hasNest
        };
    }
}
module.exports = repeatContentOfBrackets;
<|endoftext|>


<commit_before>define(
    ["require", "js/core/Element", "js/core/TextElement", "js/core/Binding", "underscore"],

    function (require, Element, TextElement, Binding, _) {

        var Template,
            Configuration;

        require(['js/core/Template'],function(TClass){
            Template = TClass;
        });
        require(['js/conf/Configuration'],function(TClass){
            Configuration = TClass;
        });

        var Component = Element.inherit("js.core.Component",
            /** @lends Component# */

            {
                /***
                 * What up??
                 * @param attributes The attributes of the component
                 * @param {String} attributes.style The style of the component
                 * @param {Node} descriptor
                 * @param {SystemManager} systemManager
                 * @param {Element} parentScope
                 * @param {Element} rootScope
                 * @constructs
                 */
                ctor: function (attributes, descriptor, systemManager, parentScope, rootScope) {

                    this.$components = [];

                    this.$templates = {};
                    this.$configurations = [];
                    this.$children = [];

                    this.callBase();
                },

                /**
                 * @name Component#ontest
                 * @event
                 * @param {Event} e Custom Event
                 * @param {String} e.$.value Your value
                 *
                 */
                events: [
                    "ontest"

                ],
                /**
                 * values to be injected
                 * @key {String} name of the variable for this.$key
                 * @value {Required Class}
                 */
                inject: {},
                _injectChain: function () {
                    return this._generateDefaultsChain("inject");
                },
                _preinitialize: function () {
                    this.callBase();

                    this._inject();
                },
                _inject: function () {

                    var inject = this._injectChain();

                    if (_.keys(inject).length > 0) {
                        // we need to inject at least on item

                        // synchronous singleton instantiation of Injection,
                        // because if module requires injection, application also depends on
                        // Injection.js and class should be installed.
                        var injection = this.$systemManager.$injection;
                        if (injection) {
                            for (var name in inject) {
                                if (inject.hasOwnProperty(name)) {
                                    this.$[name] = injection.getInstance(inject[name]);
                                }
                            }
                        } else {
                            throw "injection not available in systemManager";
                        }

                    }

                },
                addComponent: function (component) {
                    if (!component) {
                        throw "component null";
                    }

                    this.$components.push(component);

                    if (this.$rootScope && component.$.cid) {
                        // register component by cid in the root scope
                        this.$rootScope.set(component.$.cid, component);
                    }
                },

                removeComponent: function (component) {
                    // TODO: implement and remove cid from rootscope
                },

                addChild: function (child) {
                    if (!(child instanceof Element)) {
                        throw "only children of type js.core.Component can be added"
                    }

                    this.addComponent(child);

                    child.$parent = this;
                    this.$children.push(child);

                },

                removeChild: function (child) {
                    if (!(child instanceof Element)) {
                        throw "only children of type js.core.Component can be removed"
                    }

                    var index = this.$children.indexOf(child);
                    if (index != -1) {
                        // child found
                        child.$parent = null;
                        this.$children.splice(index, 1);
                    }

                    if (this.$templates.hasOwnProperty(child.$.name)) {
                        // remove it from templates
                        delete this.$templates[child.$.name];
                    }

                    index = this.$children.indexOf(child);
                    if (index != -1) {
                        this.$configurations.splice(index, 1);
                    }

                },

                addTemplate: function (template) {
                    if (!template.$.name) {
                        throw "template without name";
                    }

                    this.addComponent(template);
                    this.$templates[template.$.name] = template;
                },

                addConfiguration: function (config) {
                    this.addComponent(config);
                    this.$configurations.push(config);
                },

                getTemplate: function (name) {
                    var tpl = this.$templates[name];
                    if (tpl) {
                        return tpl;
                    } else if (this.$parent && this.$parent != this) {
                        return this.$parent.getTemplate(name);
                    } else {
                        return null
                    }
                },
                _initializeChildren: function (childComponents) {
                    for (var i = 0; i < childComponents.length; i++) {
                        // FIRST ADD CHILD
                        var child = childComponents[i];

                        if (Template && child instanceof Template) {
                            this.addTemplate(child);
                        } else if (Configuration && child instanceof Configuration) {
                            this.addConfiguration(child);
                        } else {
                            this.addChild(childComponents[i]);
                        }

                        // THEN INITIALIZE !
                        if (this.$creationPolicy == "auto") {
                            child._initialize(this.$creationPolicy);
                        }
                    }
                },
                /***
                 *
                 * @param attributes
                 */
                _initializeAttributes: function (attributes) {
                    this.callBase();

                    if (this.$creationPolicy != "full") {
                        if (attributes.hasOwnProperty("creationPolicy")) {
                            this.$creationPolicy = attributes.creationPolicy;
                            delete attributes.creationPolicy;
                        }
                    }

                },
                /***
                 *  Initializes all internal and external descriptors
                 */
                _initializeDescriptors: function () {

                    var children = [];
                    var descriptors = [];

                    // go inherit tree up and search for descriptors
                    var current = this;
                    while (current) {
                        if (current._$descriptor) {
                            descriptors.unshift(current._$descriptor);
                        }
                        current = current.base;
                    }

                    // and add outside descriptor
                    if (this.$descriptor) {
                        descriptors.push(this.$descriptor);
                    }

                    var desc, node, text;
                    for (var d = 0; d < descriptors.length; d++) {
                        desc = descriptors[d];
                        this._cleanUpDescriptor(desc);
                        children = children.concat(this._getChildrenFromDescriptor(desc));
                    }

                    this._initializeChildren(children);

                    this._childrenInitialized();
                },
                _cleanUpDescriptor: function (desc) {
                    if (desc && desc.childNodes) {
                        var node, text;
                        // remove empty text nodes
                        for (var i = desc.childNodes.length - 1; i >= 0; i--) {
                            node = desc.childNodes[i];
                            if (node.nodeType === 3) {
                                text = node.textContent || node.text || node.data;
                                if (!text || text.trim().length === 0) {
                                    desc.removeChild(node);
                                }

                            }
                        }
                    }else{
                        console.warn("Descriptor not defined or not correct");
                    }
                },
                /**
                 * an array of attributes names, which will expect handler functions
                 */
                _isEventAttribute: function (attributeName) {
                    return attributeName.indexOf("on") == 0;
                    // return this._eventAttributes.hasOwnProperty(attributeName);
                },
                /**
                 * Returns true if event is defined in Component event list
                 * @param event
                 */
                _isComponentEvent: function (event) {
                    for (var i = 0; i < this.events.length; i++) {
                        if (event == this.events[i]) {
                            return true;
                        }
                    }
                    return false;
                },
                _getEventTypeForAttribute: function (eventName) {
                    // TODO: implement eventAttribites as hash
                    return this._eventAttributes[eventName];
                },
                /***
                 * Initialize all Binding and Event attributes
                 */
                _initializeBindings: function () {
                    this.$bindings = [];
                    this.$eventDefinitions = [];
                    var attributes = this.$;

                    var self = this;
                    var binding, twoWay, eValue;
                    // Resolve bindings and events
                    for (var key in attributes) {

                        if (attributes.hasOwnProperty(key)) {
                            var value = attributes[key];

                            if (this._isEventAttribute(key)) {
                                if (this.$rootScope[value]) {

                                    this.$eventDefinitions.push({
                                        name: key,
                                        scope: this.$rootScope,
                                        fncName: value
                                    });
                                    if (this._isComponentEvent(key.substr(2))) {
                                        this.bind(key, this.$rootScope[value], this.$rootScope);
                                    }

                                } else {
                                    throw "Couldn't find callback " + value + " for " + key + " event";
                                }

                                delete attributes[key];
                            } else {
                                this.$[key] = Binding.evaluateText(value, this, key);
                            }
                        }
                    }

                    for (var c = 0; c < this.$components.length; c++) {
                        this.$components[c]._initializeBindings();
                    }

                    this.callBase();
                },
                /***
                 * Create {@link Component} for DOM Node with given attributes
                 * @param DOM Node
                 * @param attributes for new Component
                 */
                _createComponentForNode: function (node, attributes) {
                    if (!node) return null;

                    attributes = attributes || {};
                    // only instantiation and construction but no initialization
                    var appDomain = this.$systemManager.$applicationDomain;

                    if (node.nodeType == 1) { // Elements

                        var fqClassName = this.$systemManager.$applicationContext.getFqClassName(node.namespaceURI, this._localNameFromDomNode(node), true);
                        var className = this.$systemManager.$applicationContext.getFqClassName(node.namespaceURI, this._localNameFromDomNode(node), false);

                        return this.$systemManager.$applicationContext.createInstance(fqClassName, [attributes, node, this.$systemManager, this, this.$rootScope], className);

                    } else if (node.nodeType == 3) { // Textnodes
                        // remove whitespaces from text textnodes
                        var text = node.textContent ? node.textContent : node.text;
                        if (node.textContent) {
                            node.textContent = text;
                        }
                        // only instantiation and construction but no initialization
                        return this.$systemManager.$applicationContext.createInstance("js/core/TextElement", [null, node, this.$systemManager, this, this.$rootScope]);
                    }

                    return null;
                },
                /***
                 * Converts all child nodes of a descriptor to instances of Components or TextElement
                 * @param descriptor
                 */
                _getChildrenFromDescriptor: function (descriptor) {
                    var childrenFromDescriptor = [], node, component;

                    if (descriptor) {
                        for (var i = 0; i < descriptor.childNodes.length; i++) {
                            node = descriptor.childNodes[i];
                            component = this._createComponentForNode(node);
                            if (component) {
                                childrenFromDescriptor.push(component);
                            }
                        }
                    }

                    return childrenFromDescriptor;
                },
                /***
                 * @private
                 * This method is called after all children are initialized
                 */
                _childrenInitialized: function () {

                },
                /***
                 * This method should overridden by custom components to set initial variables
                 * @param scope
                 */
                initialize: function (scope) {
                },
                /**
                 * IE8 FIXES
                 * @param domNode
                 */
                _localNameFromDomNode: function (domNode) {
                    if (domNode.localName) return domNode.localName;

                    var st = domNode.tagName.split(":");
                    return st[st.length - 1];
                }
            });

        return Component;
    }
);<commit_msg>fixed circular dependency<commit_after>define(
    ["require", "js/core/Element", "js/core/TextElement", "js/core/Binding", "underscore"],

    function (require, Element, TextElement, Binding, _) {

        var Template,
            Configuration;

        var Component = Element.inherit("js.core.Component",
            /** @lends Component# */

            {
                /***
                 * What up??
                 * @param attributes The attributes of the component
                 * @param {String} attributes.style The style of the component
                 * @param {Node} descriptor
                 * @param {SystemManager} systemManager
                 * @param {Element} parentScope
                 * @param {Element} rootScope
                 * @constructs
                 */
                ctor: function (attributes, descriptor, systemManager, parentScope, rootScope) {
                    if(_.isUndefined(Template)){
                        try {
                            Template = require('js/core/Template');
                        } catch (e) {
                            Template = null;
                        }
                    }

                    if(_.isUndefined(Configuration)){
                        try {
                            Configuration = require('js/conf/Configuration');
                        } catch(e) {
                            Configuration = null;
                        }
                    }

                    this.$components = [];

                    this.$templates = {};
                    this.$configurations = [];
                    this.$children = [];

                    this.callBase();
                },

                /**
                 * @name Component#ontest
                 * @event
                 * @param {Event} e Custom Event
                 * @param {String} e.$.value Your value
                 *
                 */
                events: [
                    "ontest"

                ],
                /**
                 * values to be injected
                 * @key {String} name of the variable for this.$key
                 * @value {Required Class}
                 */
                inject: {},
                _injectChain: function () {
                    return this._generateDefaultsChain("inject");
                },
                _preinitialize: function () {
                    this.callBase();

                    this._inject();
                },
                _inject: function () {

                    var inject = this._injectChain();

                    if (_.keys(inject).length > 0) {
                        // we need to inject at least on item

                        // synchronous singleton instantiation of Injection,
                        // because if module requires injection, application also depends on
                        // Injection.js and class should be installed.
                        var injection = this.$systemManager.$injection;
                        if (injection) {
                            for (var name in inject) {
                                if (inject.hasOwnProperty(name)) {
                                    this.$[name] = injection.getInstance(inject[name]);
                                }
                            }
                        } else {
                            throw "injection not available in systemManager";
                        }

                    }

                },
                addComponent: function (component) {
                    if (!component) {
                        throw "component null";
                    }

                    this.$components.push(component);

                    if (this.$rootScope && component.$.cid) {
                        // register component by cid in the root scope
                        this.$rootScope.set(component.$.cid, component);
                    }
                },

                removeComponent: function (component) {
                    // TODO: implement and remove cid from rootscope
                },

                addChild: function (child) {
                    if (!(child instanceof Element)) {
                        throw "only children of type js.core.Component can be added"
                    }

                    this.addComponent(child);

                    child.$parent = this;
                    this.$children.push(child);

                },

                removeChild: function (child) {
                    if (!(child instanceof Element)) {
                        throw "only children of type js.core.Component can be removed"
                    }

                    var index = this.$children.indexOf(child);
                    if (index != -1) {
                        // child found
                        child.$parent = null;
                        this.$children.splice(index, 1);
                    }

                    if (this.$templates.hasOwnProperty(child.$.name)) {
                        // remove it from templates
                        delete this.$templates[child.$.name];
                    }

                    index = this.$children.indexOf(child);
                    if (index != -1) {
                        this.$configurations.splice(index, 1);
                    }

                },

                addTemplate: function (template) {
                    if (!template.$.name) {
                        throw "template without name";
                    }

                    this.addComponent(template);
                    this.$templates[template.$.name] = template;
                },

                addConfiguration: function (config) {
                    this.addComponent(config);
                    this.$configurations.push(config);
                },

                getTemplate: function (name) {
                    var tpl = this.$templates[name];
                    if (tpl) {
                        return tpl;
                    } else if (this.$parent && this.$parent != this) {
                        return this.$parent.getTemplate(name);
                    } else {
                        return null
                    }
                },
                _initializeChildren: function (childComponents) {
                    for (var i = 0; i < childComponents.length; i++) {
                        // FIRST ADD CHILD
                        var child = childComponents[i];

                        if (Template && child instanceof Template) {
                            this.addTemplate(child);
                        } else if (Configuration && child instanceof Configuration) {
                            this.addConfiguration(child);
                        } else {
                            this.addChild(childComponents[i]);
                        }

                        // THEN INITIALIZE !
                        if (this.$creationPolicy == "auto") {
                            child._initialize(this.$creationPolicy);
                        }
                    }
                },
                /***
                 *
                 * @param attributes
                 */
                _initializeAttributes: function (attributes) {
                    this.callBase();

                    if (this.$creationPolicy != "full") {
                        if (attributes.hasOwnProperty("creationPolicy")) {
                            this.$creationPolicy = attributes.creationPolicy;
                            delete attributes.creationPolicy;
                        }
                    }

                },
                /***
                 *  Initializes all internal and external descriptors
                 */
                _initializeDescriptors: function () {

                    var children = [];
                    var descriptors = [];

                    // go inherit tree up and search for descriptors
                    var current = this;
                    while (current) {
                        if (current._$descriptor) {
                            descriptors.unshift(current._$descriptor);
                        }
                        current = current.base;
                    }

                    // and add outside descriptor
                    if (this.$descriptor) {
                        descriptors.push(this.$descriptor);
                    }

                    var desc, node, text;
                    for (var d = 0; d < descriptors.length; d++) {
                        desc = descriptors[d];
                        this._cleanUpDescriptor(desc);
                        children = children.concat(this._getChildrenFromDescriptor(desc));
                    }

                    this._initializeChildren(children);

                    this._childrenInitialized();
                },
                _cleanUpDescriptor: function (desc) {
                    if (desc && desc.childNodes) {
                        var node, text;
                        // remove empty text nodes
                        for (var i = desc.childNodes.length - 1; i >= 0; i--) {
                            node = desc.childNodes[i];
                            if (node.nodeType === 3) {
                                text = node.textContent || node.text || node.data;
                                if (!text || text.trim().length === 0) {
                                    desc.removeChild(node);
                                }

                            }
                        }
                    }else{
                        console.warn("Descriptor not defined or not correct");
                    }
                },
                /**
                 * an array of attributes names, which will expect handler functions
                 */
                _isEventAttribute: function (attributeName) {
                    return attributeName.indexOf("on") == 0;
                    // return this._eventAttributes.hasOwnProperty(attributeName);
                },
                /**
                 * Returns true if event is defined in Component event list
                 * @param event
                 */
                _isComponentEvent: function (event) {
                    for (var i = 0; i < this.events.length; i++) {
                        if (event == this.events[i]) {
                            return true;
                        }
                    }
                    return false;
                },
                _getEventTypeForAttribute: function (eventName) {
                    // TODO: implement eventAttribites as hash
                    return this._eventAttributes[eventName];
                },
                /***
                 * Initialize all Binding and Event attributes
                 */
                _initializeBindings: function () {
                    this.$bindings = [];
                    this.$eventDefinitions = [];
                    var attributes = this.$;

                    var self = this;
                    var binding, twoWay, eValue;
                    // Resolve bindings and events
                    for (var key in attributes) {

                        if (attributes.hasOwnProperty(key)) {
                            var value = attributes[key];

                            if (this._isEventAttribute(key)) {
                                if (this.$rootScope[value]) {

                                    this.$eventDefinitions.push({
                                        name: key,
                                        scope: this.$rootScope,
                                        fncName: value
                                    });
                                    if (this._isComponentEvent(key.substr(2))) {
                                        this.bind(key, this.$rootScope[value], this.$rootScope);
                                    }

                                } else {
                                    throw "Couldn't find callback " + value + " for " + key + " event";
                                }

                                delete attributes[key];
                            } else {
                                this.$[key] = Binding.evaluateText(value, this, key);
                            }
                        }
                    }

                    for (var c = 0; c < this.$components.length; c++) {
                        this.$components[c]._initializeBindings();
                    }

                    this.callBase();
                },
                /***
                 * Create {@link Component} for DOM Node with given attributes
                 * @param DOM Node
                 * @param attributes for new Component
                 */
                _createComponentForNode: function (node, attributes) {
                    if (!node) return null;

                    attributes = attributes || {};
                    // only instantiation and construction but no initialization
                    var appDomain = this.$systemManager.$applicationDomain;

                    if (node.nodeType == 1) { // Elements

                        var fqClassName = this.$systemManager.$applicationContext.getFqClassName(node.namespaceURI, this._localNameFromDomNode(node), true);
                        var className = this.$systemManager.$applicationContext.getFqClassName(node.namespaceURI, this._localNameFromDomNode(node), false);

                        return this.$systemManager.$applicationContext.createInstance(fqClassName, [attributes, node, this.$systemManager, this, this.$rootScope], className);

                    } else if (node.nodeType == 3) { // Textnodes
                        // remove whitespaces from text textnodes
                        var text = node.textContent ? node.textContent : node.text;
                        if (node.textContent) {
                            node.textContent = text;
                        }
                        // only instantiation and construction but no initialization
                        return this.$systemManager.$applicationContext.createInstance("js/core/TextElement", [null, node, this.$systemManager, this, this.$rootScope]);
                    }

                    return null;
                },
                /***
                 * Converts all child nodes of a descriptor to instances of Components or TextElement
                 * @param descriptor
                 */
                _getChildrenFromDescriptor: function (descriptor) {
                    var childrenFromDescriptor = [], node, component;

                    if (descriptor) {
                        for (var i = 0; i < descriptor.childNodes.length; i++) {
                            node = descriptor.childNodes[i];
                            component = this._createComponentForNode(node);
                            if (component) {
                                childrenFromDescriptor.push(component);
                            }
                        }
                    }

                    return childrenFromDescriptor;
                },
                /***
                 * @private
                 * This method is called after all children are initialized
                 */
                _childrenInitialized: function () {

                },
                /***
                 * This method should overridden by custom components to set initial variables
                 * @param scope
                 */
                initialize: function (scope) {
                },
                /**
                 * IE8 FIXES
                 * @param domNode
                 */
                _localNameFromDomNode: function (domNode) {
                    if (domNode.localName) return domNode.localName;

                    var st = domNode.tagName.split(":");
                    return st[st.length - 1];
                }
            });

        return Component;
    }
);<|endoftext|>


<commit_before>document.addEventListener('DOMContentLoaded', async () => {
	function formatNumber(number) {
		return number.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.');
	}

	function formatDate(date) {
		let month = date.getMonth() + 1;
		if (month < 10) month = `0${month}`;

		let day = date.getDate();
		if (day < 10) day = `0${day}`;

		return `${day}.${month}.${date.getFullYear()}`;
	}

	function updateMilestones(ms) {
		const milestonesWrap = document.getElementById('milestones-wrap');
		milestonesWrap.innerHTML = '';

		if (ms.length === 0) {
			const warning = document.createElement('h3');
			warning.id = 'warning';
			warning.innerText = 'No milestones available.';

			return milestonesWrap.appendChild(warning);
		}

		const milestonesList = document.createElement('ol');

		for (milestone of ms) {
			const milestoneListElement = document.createElement('li');
			milestoneListElement.classList.add('milestones');
			milestoneListElement.id = `milestone-${milestone.id}`;

			const milestoneHeader = document.createElement('h3');
			milestoneHeader.innerText = `Milestone ${milestone.id}`;

			milestoneListElement.appendChild(milestoneHeader);

			const milestoneCount = document.createElement('p');
			milestoneCount.innerText = `Clicks to reach: ${formatNumber(milestone.count)}`;

			milestoneListElement.appendChild(milestoneCount);

			const milestoneStatus = document.createElement('p');
			milestoneStatus.innerText = `Milestone reached: ${milestone.reached === 0 ? 'No' : 'Yes'}`;

			milestoneListElement.appendChild(milestoneStatus);

			if (milestone.reached) {
				const formattedDate = formatDate(new Date(milestone.timestamp));

				const milestoneDate = document.createElement('p');
				milestoneDate.innerText = `Count reached on: ${milestone.timestamp ? formattedDate : 'Unknown'}`;

				milestoneListElement.appendChild(milestoneDate);

				const milestoneSoundElement = document.createElement('p');
				const soundObject = sounds.find(s => s.id === milestone.sound_id);
				milestoneSoundElement.innerText = `Sound that played: ${soundObject ? soundObject.displayname : 'Unknown'}`;

				milestoneListElement.appendChild(milestoneSoundElement);
			}

			milestonesList.appendChild(milestoneListElement);
		}

		milestonesWrap.appendChild(milestonesList);
	}

	const milestones = await fetch('/api/statistics/milestones').then(res => res.json());
	const sounds = await fetch('/api/sounds').then(res => res.json());
	updateMilestones(milestones);

	const conInfo = await fetch('/api/conInfo').then(res => res.json());

	const domainOrIP = document.URL.split('/')[2].split(':')[0];
	const host = conInfo.ssl ? `wss://${domainOrIP}` : `ws://${domainOrIP}:${conInfo.port}`;

	const ws = new WebSocket(host);

	ws.addEventListener('open', event => {
		ws.addEventListener('message', message => {
			let data;

			try {
				data = JSON.parse(message.data);
			}
			catch (e) {
				data = {};
			}

			switch (data.type) {
				case 'notification':
					if (data.notification) document.getElementById('notification').innerText = data.notification.text;
					util.fade(document.getElementById('notification-wrapper'), data.notification.duration * 1000, 0.1);
					break;
				case 'milestoneUpdate':
				case 'milestoneModify':
					milestones[milestones.findIndex(ms => ms.id === data.milestone.id)] = data.milestone;
					updateMilestones(milestones);
					break;
				case 'milestoneAdd':
					milestones.push(data.milestone);
					updateMilestones(milestones);
					break;
				case 'milestoneDelete':
					milestones.splice(milestones.findIndex(ms => ms.id === data.milestone.id), 1);
					updateMilestones(milestones);
					break;
				case 'soundModify':
					sounds[sounds.findIndex(snd => snd.id === data.sound.id)] = data.sound;
					break;
				case 'soundUpload':
					sounds.push(data.sound);
					break;
				case 'soundDelete':
					sounds.splice(sounds.findIndex(snd => snd.id === data.sound.id), 1);
					break;
				default:
					break;
			}
		});
	});
});<commit_msg>Reverse order milestones in frontend<commit_after>document.addEventListener('DOMContentLoaded', async () => {
	function formatNumber(number) {
		return number.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.');
	}

	function formatDate(date) {
		let month = date.getMonth() + 1;
		if (month < 10) month = `0${month}`;

		let day = date.getDate();
		if (day < 10) day = `0${day}`;

		return `${day}.${month}.${date.getFullYear()}`;
	}

	function updateMilestones(ms) {
		ms = ms.sort((a, b) => b.count - a.count); // Reverse order

		const milestonesWrap = document.getElementById('milestones-wrap');
		milestonesWrap.innerHTML = '';

		if (ms.length === 0) {
			const warning = document.createElement('h3');
			warning.id = 'warning';
			warning.innerText = 'No milestones available.';

			return milestonesWrap.appendChild(warning);
		}

		const milestonesList = document.createElement('ol');

		for (milestone of ms) {
			const milestoneListElement = document.createElement('li');
			milestoneListElement.classList.add('milestones');
			milestoneListElement.id = `milestone-${milestone.id}`;

			const milestoneHeader = document.createElement('h3');
			milestoneHeader.innerText = `Milestone ${milestone.id}`;

			milestoneListElement.appendChild(milestoneHeader);

			const milestoneCount = document.createElement('p');
			milestoneCount.innerText = `Clicks to reach: ${formatNumber(milestone.count)}`;

			milestoneListElement.appendChild(milestoneCount);

			const milestoneStatus = document.createElement('p');
			milestoneStatus.innerText = `Milestone reached: ${milestone.reached === 0 ? 'No' : 'Yes'}`;

			milestoneListElement.appendChild(milestoneStatus);

			if (milestone.reached) {
				const formattedDate = formatDate(new Date(milestone.timestamp));

				const milestoneDate = document.createElement('p');
				milestoneDate.innerText = `Count reached on: ${milestone.timestamp ? formattedDate : 'Unknown'}`;

				milestoneListElement.appendChild(milestoneDate);

				const milestoneSoundElement = document.createElement('p');
				const soundObject = sounds.find(s => s.id === milestone.sound_id);
				milestoneSoundElement.innerText = `Sound that played: ${soundObject ? soundObject.displayname : 'Unknown'}`;

				milestoneListElement.appendChild(milestoneSoundElement);
			}

			milestonesList.appendChild(milestoneListElement);
		}

		milestonesWrap.appendChild(milestonesList);
	}

	const milestones = await fetch('/api/statistics/milestones').then(res => res.json());
	const sounds = await fetch('/api/sounds').then(res => res.json());
	updateMilestones(milestones);

	const conInfo = await fetch('/api/conInfo').then(res => res.json());

	const domainOrIP = document.URL.split('/')[2].split(':')[0];
	const host = conInfo.ssl ? `wss://${domainOrIP}` : `ws://${domainOrIP}:${conInfo.port}`;

	const ws = new WebSocket(host);

	ws.addEventListener('open', event => {
		ws.addEventListener('message', message => {
			let data;

			try {
				data = JSON.parse(message.data);
			}
			catch (e) {
				data = {};
			}

			switch (data.type) {
				case 'notification':
					if (data.notification) document.getElementById('notification').innerText = data.notification.text;
					util.fade(document.getElementById('notification-wrapper'), data.notification.duration * 1000, 0.1);
					break;
				case 'milestoneUpdate':
				case 'milestoneModify':
					milestones[milestones.findIndex(ms => ms.id === data.milestone.id)] = data.milestone;
					updateMilestones(milestones);
					break;
				case 'milestoneAdd':
					milestones.push(data.milestone);
					updateMilestones(milestones);
					break;
				case 'milestoneDelete':
					milestones.splice(milestones.findIndex(ms => ms.id === data.milestone.id), 1);
					updateMilestones(milestones);
					break;
				case 'soundModify':
					sounds[sounds.findIndex(snd => snd.id === data.sound.id)] = data.sound;
					break;
				case 'soundUpload':
					sounds.push(data.sound);
					break;
				case 'soundDelete':
					sounds.splice(sounds.findIndex(snd => snd.id === data.sound.id), 1);
					break;
				default:
					break;
			}
		});
	});
});<|endoftext|>


<commit_before>import Assignment from './assignment';
import { clone, extend } from './lib/utils';

class Experiment {
	constructor(inputs) {
		this.logger_configured = false;
		this.inputs = inputs;
		this._exposure_logged = false;
		this._salt = null;
		this._in_experiment = true;

		this.name = "EXPERIMENT";
		this._auto_exposure_log = true;

		this.setup();

		this._assignment = new Assignment(this.get_salt());
		this._assigned = false;
	}

	require_assignment() {
		if (!this._assigned) {
			this._assign();
		}
	}

	require_exposure_logging() {
		if (this._auto_exposure_log && !this._exposure_logged) {
			this.log_exposure();
		}
	}

	_assign() {
		this.configure_logger();
		this.assign(this._assignment, this.inputs);
		this._assigned = true;
	}

	setup() {
		return;
	}

	in_experiment() {
		return this._in_experiment;
	}

	set_overrides(value) {
		this._assignment.set_overrides(value);
		var o = this._assignment.get_overrides();
		var self = this;
		Object.keys(o).forEach(function(key) {
			if (self.inputs[key] !== undefined) {
				self.inputs[key] = o[key];
			}
		});
	}

	get_salt() {
		if (this._salt) {
			return this._salt;
		} else {
			return this.name;
		}
	}

	set_salt(value) {
		this._salt = value;
		if(this._assignment) {
			this._assignment.experiment_salt = value;
		}
	}

	get_name() {
		return this._name;
	}

	assign(params, args) {
		throw "IMPLEMENT THIS";
	}

	set_name(value) {
		var re = /\s+/g;
		var name = value.replace(re, '-')
		if (this._assignment) {
			this._assignment.experiment_salt = this.get_salt();
		}
	}

	__asBlob(extras) {
		if (!extras) { extras = {}; }

		var d = {
			'name': this.get_name(),
			'time': new Date().getTime() / 1000,
			'salt': this.get_salt(),
			'inputs': this.inputs,
			'params': this._assignment.get_params()
		};
		extend(d, extras);
    return d;
	}

	set_auto_exposure_logging(value) {
		this._auto_exposure_log = value;
	}

	get_params() {
		this.require_assignment();
		this.require_exposure_logging();
		return this._assignment.get_params();
	}

	get(name, def) {
		this.require_assignment();
		this.require_exposure_logging();
		return this._assignment.get(name, def);
	}

	toString() {
		this.require_assignment();
		this.require_exposure_logging();
		return JSON.stringify(this.__asBlob());
	}

	log_exposure(extras) {
		if (!this._in_experiment) {
			return;
		}
		this._exposure_logged = true;
		this.log_event('exposure', extras);
	}

	log_event(event_type, extras) {
		if (!this._in_experiment) {
			return;
		}

		var extra_payload;

		if(extras) {
			extra_payload = { 'event': event_type, 'extra_data': clone(extras)};
		} else {
			extra_payload = { 'event': event_type };
		}

		this.log(this.__asBlob(extra_payload));
	}

	configure_logger() {
		throw "IMPLEMENT THIS";
	}

	log(data) {
		throw "IMPLEMENT THIS";
	}

	previously_logged() {
		throw "IMPLEMENT THIS";
	}
}

export default Experiment;
<commit_msg>assign experiment names<commit_after>import Assignment from './assignment';
import { clone, extend, isObject } from './lib/utils';

class Experiment {
	constructor(inputs) {
		this.logger_configured = false;
		this.inputs = inputs;
		this._exposure_logged = false;
		this._salt = null;
		this._in_experiment = true;

		this.name = this.getDefaultExperimentName();
		this._auto_exposure_log = true;

		this.setup();

		this._assignment = new Assignment(this.get_salt());
		this._assigned = false;
	}

  //helper function to return the class name of the current experiment class
  getDefaultExperimentName() {
    if (isObject(this) && this.constructor && this !== this.window) {
      var arr = this.constructor.toString().match(/function\s*(\w+)/);
      if (arr && arr.length === 2) {
        return arr[1];
      }
    }
    return "GenericExperiment";
  }

	require_assignment() {
		if (!this._assigned) {
			this._assign();
		}
	}

	require_exposure_logging() {
		if (this._auto_exposure_log && !this._exposure_logged) {
			this.log_exposure();
		}
	}

	_assign() {
		this.configure_logger();
		this.assign(this._assignment, this.inputs);
		this._assigned = true;
	}

	setup() {
		return;
	}

	in_experiment() {
		return this._in_experiment;
	}

	set_overrides(value) {
		this._assignment.set_overrides(value);
		var o = this._assignment.get_overrides();
		var self = this;
		Object.keys(o).forEach(function(key) {
			if (self.inputs[key] !== undefined) {
				self.inputs[key] = o[key];
			}
		});
	}

	get_salt() {
		if (this._salt) {
			return this._salt;
		} else {
			return this.name;
		}
	}

	set_salt(value) {
		this._salt = value;
		if(this._assignment) {
			this._assignment.experiment_salt = value;
		}
	}

	get_name() {
		return this._name;
	}

	assign(params, args) {
		throw "IMPLEMENT THIS";
	}

	set_name(value) {
		var re = /\s+/g;
		var name = value.replace(re, '-')
		if (this._assignment) {
			this._assignment.experiment_salt = this.get_salt();
		}
	}

	__asBlob(extras) {
		if (!extras) { extras = {}; }

		var d = {
			'name': this.get_name(),
			'time': new Date().getTime() / 1000,
			'salt': this.get_salt(),
			'inputs': this.inputs,
			'params': this._assignment.get_params()
		};
		extend(d, extras);
    return d;
	}

	set_auto_exposure_logging(value) {
		this._auto_exposure_log = value;
	}

	get_params() {
		this.require_assignment();
		this.require_exposure_logging();
		return this._assignment.get_params();
	}

	get(name, def) {
		this.require_assignment();
		this.require_exposure_logging();
		return this._assignment.get(name, def);
	}

	toString() {
		this.require_assignment();
		this.require_exposure_logging();
		return JSON.stringify(this.__asBlob());
	}

	log_exposure(extras) {
		if (!this._in_experiment) {
			return;
		}
		this._exposure_logged = true;
		this.log_event('exposure', extras);
	}

	log_event(event_type, extras) {
		if (!this._in_experiment) {
			return;
		}

		var extra_payload;

		if(extras) {
			extra_payload = { 'event': event_type, 'extra_data': clone(extras)};
		} else {
			extra_payload = { 'event': event_type };
		}

		this.log(this.__asBlob(extra_payload));
	}

	configure_logger() {
		throw "IMPLEMENT THIS";
	}

	log(data) {
		throw "IMPLEMENT THIS";
	}

	previously_logged() {
		throw "IMPLEMENT THIS";
	}
}

export default Experiment;
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'

import './Clock.css'

import Weather from './Weather.js'
import Hand from './Hand.js'
import Error from './Error.js'
import ClockCoordinates from './ClockCoordinates.js'

const HOUR_HAND_LENGTH = 23
const MINUTE_HAND_LENGTH = 34

class Clock extends React.Component {
  constructor (props) {
    super(props)

    this.state = this._getInitialState()
  }

  _getInitialState = () => {
    if (navigator.geolocation) {
      return {
        now: this.props.now,
        position: null,
        progress: null,
        forecast: null,
        error: null
      }
    }

    return {
      now: this.props.now,
      position: null,
      progress: null,
      forecast: null,

      // FIXME: Add a link for contacting me with browser information
      error: (
        <Error title='Geolocation unsupported' reload={this.props.reload}>
          Your browser does not support geolocation. Without knowing your current position, showing
          your local weather forecast is not possible.

Please try another browser.

        </Error>
      )
    }
  }

  componentDidMount = () => {
    if (!this.state.position && !this.state.progress && !this.state.error) {
      console.log('Geolocating...')
      this.setState({
        progress: <text className='progress'>Locating phone...</text>
      })
      navigator.geolocation.getCurrentPosition(this.setPosition, this.geoError)
    }
  }

  componentDidUpdate = () => {
    if (this.props.now !== this.state.now) {
      this.setState(this._getInitialState())
    }

    if (!this.state.position && !this.state.progress && !this.state.error) {
      console.log('Geolocating...')
      this.setState({
        progress: <text className='progress'>Locating phone...</text>
      })
      navigator.geolocation.getCurrentPosition(this.setPosition, this.geoError)
    }
  }

  setPosition = position => {
    const latitude = position.coords.latitude
    const longitude = position.coords.longitude
    console.log(`got position: ${latitude} ${longitude}`)
    this.setState({
      progress: <text className='progress'>Downloading weather...</text>,
      position: position.coords
    })

    this.download_weather(latitude, longitude)
  }

  download_weather = (latitude, longitude) => {
    const url = `https://api-met-no-proxy.appspot.com/weatherapi/locationforecast/1.9/?lat=${latitude};lon=${longitude}`
    console.log('Getting weather from: ' + url)

    const self = this

    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error(`Response code from upstream: ${response.status}`)
        }
        return response.text()
      })
      .then(weatherXmlString => {
        const forecast = self.parseWeatherXml(weatherXmlString)

        self.setState({ forecast: forecast })
      })
      .catch(error => {
        this.setState({
          error: (
            <Error title='Downloading weather failed' reload={this.props.reload}>
              {error.message}
            </Error>
          )
        })
      })
  }

  /* Parses weather XML from yr.no into a weather object that maps timestamps (in
   * seconds since the epoch) to forecasts. A forecast has these fields:
   *
   * .celsius: The forecasted temperatures in centigrades
   *
   * .wind_m_s: The forecasted wind speed
   *
   * .symbol: The weather symbol index. Resolve using
   *         https://api.yr.no/weatherapi/weathericon
   */
  parseWeatherXml = weatherXmlString => {
    const weatherXml = new window.DOMParser().parseFromString(weatherXmlString, 'text/xml')
    const allPrognoses = weatherXml.getElementsByTagName('time')
    console.log('Parsing ' + allPrognoses.length + ' prognoses...')

    const forecasts = {}
    for (var i = 0; i < allPrognoses.length; i++) {
      const prognosis = allPrognoses[i]

      const from = new Date(prognosis.attributes.from.value)
      const to = new Date(prognosis.attributes.to.value)
      const dh = (to.getTime() - from.getTime()) / (3600 * 1000)
      const timestamp = new Date((from.getTime() + to.getTime()) / 2)

      var forecast = forecasts[timestamp]
      if (!forecast) {
        forecast = {}
      }

      forecast.timestamp = timestamp

      if (forecast.span_h !== undefined && forecast.span_h <= dh) {
        // There's already better data here
        continue
      }

      forecast.span_h = dh

      const symbolNodes = prognosis.getElementsByTagName('symbol')
      if (symbolNodes && symbolNodes.length > 0) {
        const symbolNumber = symbolNodes[0].attributes.number.value
        forecast.symbol = symbolNumber
      }

      const celsiusNodes = prognosis.getElementsByTagName('temperature')
      if (celsiusNodes && celsiusNodes.length > 0) {
        const celsiusValue = celsiusNodes[0].attributes.value.value
        forecast.celsius = parseFloat(celsiusValue)
      }

      const windNodes = prognosis.getElementsByTagName('windSpeed')
      if (windNodes && windNodes.length > 0) {
        const windValue = windNodes[0].attributes.mps.value
        forecast.wind_m_s = parseFloat(windValue)
      }

      const precipitationNodes = prognosis.getElementsByTagName('precipitation')
      if (precipitationNodes && precipitationNodes.length > 0) {
        const precipitationValue = precipitationNodes[0].attributes.value.value
        forecast.precipitation_mm = parseFloat(precipitationValue)
      }

      forecasts[timestamp] = forecast
    }

    return forecasts
  }

  geoError = error => {
    console.log('Geolocation failed')
    this.setState({
      // FIXME: Add a report-problem link?
      // FIXME: Make the error message text clickable and link it to a Google search
      // Reload trickery from: https://stackoverflow.com/a/10840058/473672
      error: (
        <Error title='Geolocation failed' reload={window.location.reload.bind(window.location)}>
          {error.message}
        </Error>
      )
    })
  }

  renderHands = () => {
    const nowCoords = new ClockCoordinates(this.state.now)

    // FIXME: This doubles the center circle shadow, maybe draw
    // the center circle once here to get us only one of those?
    return (
      <React.Fragment>
        <Hand
          width={2.5}
          dx={nowCoords.hourDx(HOUR_HAND_LENGTH)}
          dy={nowCoords.hourDy(HOUR_HAND_LENGTH)}
        />
        <Hand
          width={2}
          dx={nowCoords.minuteDx(MINUTE_HAND_LENGTH)}
          dy={nowCoords.minuteDy(MINUTE_HAND_LENGTH)}
        />
      </React.Fragment>
    )
  }

  render = () => {
    return (
      <React.Fragment>
        <svg
          id='weatherclock'
          xmlns='http://www.w3.org/2000/svg'
          version='1.1'
          viewBox='-50 -50 100 100'
        >
          <image x='-50' y='-50' width='100' height='100' xlinkHref='clock-frame.png' />

          {this.getClockContents()}
        </svg>
        {this.state.error}
      </React.Fragment>
    )
  }

  getClockContents = () => {
    if (this.state.error) {
      // The hands will show up behind the error dialog
      return this.renderHands()
    }

    if (this.state.forecast) {
      return (
        <React.Fragment>
          <Weather forecast={this.state.forecast} now={this.state.now} />
          {this.renderHands()}
        </React.Fragment>
      )
    }

    if (this.state.progress) {
      return this.state.progress
    }

    // Most likely the initial state
    return null
  }
}

Clock.propTypes = {
  now: PropTypes.instanceOf(Date).isRequired,
  reload: PropTypes.func.isRequired
}

export default Clock
<commit_msg>Make positioning-failed alert somewhat less bad<commit_after>import React from 'react'
import PropTypes from 'prop-types'

import './Clock.css'

import Weather from './Weather.js'
import Hand from './Hand.js'
import Error from './Error.js'
import ClockCoordinates from './ClockCoordinates.js'

const HOUR_HAND_LENGTH = 23
const MINUTE_HAND_LENGTH = 34

class Clock extends React.Component {
  constructor (props) {
    super(props)

    this.state = this._getInitialState()
  }

  _getInitialState = () => {
    if (navigator.geolocation) {
      return {
        now: this.props.now,
        position: null,
        progress: null,
        forecast: null,
        error: null
      }
    }

    return {
      now: this.props.now,
      position: null,
      progress: null,
      forecast: null,

      // FIXME: Add a link for contacting me with browser information
      error: (
        <Error title='Geolocation unsupported' reload={this.props.reload}>
          Your browser does not support geolocation. Without knowing your current position, showing
          your local weather forecast is not possible.

Please try another browser.

        </Error>
      )
    }
  }

  componentDidMount = () => {
    if (!this.state.position && !this.state.progress && !this.state.error) {
      console.log('Geolocating...')
      this.setState({
        progress: <text className='progress'>Locating phone...</text>
      })
      navigator.geolocation.getCurrentPosition(this.setPosition, this.geoError)
    }
  }

  componentDidUpdate = () => {
    if (this.props.now !== this.state.now) {
      this.setState(this._getInitialState())
    }

    if (!this.state.position && !this.state.progress && !this.state.error) {
      console.log('Geolocating...')
      this.setState({
        progress: <text className='progress'>Locating phone...</text>
      })
      navigator.geolocation.getCurrentPosition(this.setPosition, this.geoError)
    }
  }

  setPosition = position => {
    const latitude = position.coords.latitude
    const longitude = position.coords.longitude
    console.log(`got position: ${latitude} ${longitude}`)
    this.setState({
      progress: <text className='progress'>Downloading weather...</text>,
      position: position.coords
    })

    this.download_weather(latitude, longitude)
  }

  download_weather = (latitude, longitude) => {
    const url = `https://api-met-no-proxy.appspot.com/weatherapi/locationforecast/1.9/?lat=${latitude};lon=${longitude}`
    console.log('Getting weather from: ' + url)

    const self = this

    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error(`Response code from upstream: ${response.status}`)
        }
        return response.text()
      })
      .then(weatherXmlString => {
        const forecast = self.parseWeatherXml(weatherXmlString)

        self.setState({ forecast: forecast })
      })
      .catch(error => {
        this.setState({
          error: (
            <Error title='Downloading weather failed' reload={this.props.reload}>
              {error.message}
            </Error>
          )
        })
      })
  }

  /* Parses weather XML from yr.no into a weather object that maps timestamps (in
   * seconds since the epoch) to forecasts. A forecast has these fields:
   *
   * .celsius: The forecasted temperatures in centigrades
   *
   * .wind_m_s: The forecasted wind speed
   *
   * .symbol: The weather symbol index. Resolve using
   *         https://api.yr.no/weatherapi/weathericon
   */
  parseWeatherXml = weatherXmlString => {
    const weatherXml = new window.DOMParser().parseFromString(weatherXmlString, 'text/xml')
    const allPrognoses = weatherXml.getElementsByTagName('time')
    console.log('Parsing ' + allPrognoses.length + ' prognoses...')

    const forecasts = {}
    for (var i = 0; i < allPrognoses.length; i++) {
      const prognosis = allPrognoses[i]

      const from = new Date(prognosis.attributes.from.value)
      const to = new Date(prognosis.attributes.to.value)
      const dh = (to.getTime() - from.getTime()) / (3600 * 1000)
      const timestamp = new Date((from.getTime() + to.getTime()) / 2)

      var forecast = forecasts[timestamp]
      if (!forecast) {
        forecast = {}
      }

      forecast.timestamp = timestamp

      if (forecast.span_h !== undefined && forecast.span_h <= dh) {
        // There's already better data here
        continue
      }

      forecast.span_h = dh

      const symbolNodes = prognosis.getElementsByTagName('symbol')
      if (symbolNodes && symbolNodes.length > 0) {
        const symbolNumber = symbolNodes[0].attributes.number.value
        forecast.symbol = symbolNumber
      }

      const celsiusNodes = prognosis.getElementsByTagName('temperature')
      if (celsiusNodes && celsiusNodes.length > 0) {
        const celsiusValue = celsiusNodes[0].attributes.value.value
        forecast.celsius = parseFloat(celsiusValue)
      }

      const windNodes = prognosis.getElementsByTagName('windSpeed')
      if (windNodes && windNodes.length > 0) {
        const windValue = windNodes[0].attributes.mps.value
        forecast.wind_m_s = parseFloat(windValue)
      }

      const precipitationNodes = prognosis.getElementsByTagName('precipitation')
      if (precipitationNodes && precipitationNodes.length > 0) {
        const precipitationValue = precipitationNodes[0].attributes.value.value
        forecast.precipitation_mm = parseFloat(precipitationValue)
      }

      forecasts[timestamp] = forecast
    }

    return forecasts
  }

  geoError = error => {
    console.log('Geolocation failed')
    this.setState({
      // FIXME: Add a report-problem link?
      // FIXME: Make the error message text clickable and link it to a Google search
      // Reload trickery from: https://stackoverflow.com/a/10840058/473672
      error: (
        <Error title={error.message} reload={window.location.reload.bind(window.location)}>
          If you are asked whether to allow the Weather Clock to know your current location, please
          say "yes".
        </Error>
      )
    })
  }

  renderHands = () => {
    const nowCoords = new ClockCoordinates(this.state.now)

    // FIXME: This doubles the center circle shadow, maybe draw
    // the center circle once here to get us only one of those?
    return (
      <React.Fragment>
        <Hand
          width={2.5}
          dx={nowCoords.hourDx(HOUR_HAND_LENGTH)}
          dy={nowCoords.hourDy(HOUR_HAND_LENGTH)}
        />
        <Hand
          width={2}
          dx={nowCoords.minuteDx(MINUTE_HAND_LENGTH)}
          dy={nowCoords.minuteDy(MINUTE_HAND_LENGTH)}
        />
      </React.Fragment>
    )
  }

  render = () => {
    return (
      <React.Fragment>
        <svg
          id='weatherclock'
          xmlns='http://www.w3.org/2000/svg'
          version='1.1'
          viewBox='-50 -50 100 100'
        >
          <image x='-50' y='-50' width='100' height='100' xlinkHref='clock-frame.png' />

          {this.getClockContents()}
        </svg>
        {this.state.error}
      </React.Fragment>
    )
  }

  getClockContents = () => {
    if (this.state.error) {
      // The hands will show up behind the error dialog
      return this.renderHands()
    }

    if (this.state.forecast) {
      return (
        <React.Fragment>
          <Weather forecast={this.state.forecast} now={this.state.now} />
          {this.renderHands()}
        </React.Fragment>
      )
    }

    if (this.state.progress) {
      return this.state.progress
    }

    // Most likely the initial state
    return null
  }
}

Clock.propTypes = {
  now: PropTypes.instanceOf(Date).isRequired,
  reload: PropTypes.func.isRequired
}

export default Clock
<|endoftext|>