<commit_before>import React, { cloneElement, isValidElement } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import classNames from 'classnames';
import Text from './Text';
import { getPresentationAttributes, findAllByType } from '../util/ReactUtils';
import { isNumOrStr, isNumber, isPercent, getPercentValue, uniqueId,
  mathSign } from '../util/DataUtils';
import { polarToCartesian } from '../util/PolarUtils';

const cartesianViewBoxShape = PropTypes.shape({
  x: PropTypes.number,
  y: PropTypes.number,
  width: PropTypes.number,
  height: PropTypes.number,
});
const polarViewBoxShape = PropTypes.shape({
  cx: PropTypes.number,
  cy: PropTypes.number,
  innerRadius: PropTypes.number,
  outerRadius: PropTypes.number,
  startAngle: PropTypes.number,
  endAngle: PropTypes.number,
});

const propTypes = {
  viewBox: PropTypes.oneOfType([cartesianViewBoxShape, polarViewBoxShape]),
  formatter: PropTypes.func,
  value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  offset: PropTypes.number,
  position: PropTypes.oneOf([
    'top', 'left', 'right', 'bottom', 'inside', 'outside',
    'insideLeft', 'insideRight', 'insideTop', 'insideBottom',
    'insideTopLeft', 'insideBottomLeft', 'insideTopRight', 'insideBottomRight',
    'insideStart', 'insideEnd', 'end', 'center',
  ]),
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]),
  className: PropTypes.string,
  content: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
};

const defaultProps = {
  offset: 5,
};

const getLabel = (props) => {
  const { value, formatter } = props;
  const label = _.isNil(props.children) ? value : props.children;

  if (_.isFunction(formatter)) {
    return formatter(label);
  }

  return label;
};

const getDeltaAngle = (startAngle, endAngle) => {
  const sign = mathSign(endAngle - startAngle);
  const deltaAngle = Math.min(Math.abs(endAngle - startAngle), 360);

  return sign * deltaAngle;
};

const renderRadialLabel = (labelProps, label, attrs) => {
  const { position, viewBox, offset, className } = labelProps;
  const { cx, cy, innerRadius, outerRadius, startAngle, endAngle,
    clockWise } = viewBox;
  const radius = (innerRadius + outerRadius) / 2;
  const deltaAngle = getDeltaAngle(startAngle, endAngle);
  const sign = deltaAngle >= 0 ? 1 : -1;
  let labelAngle, direction;

  if (position === 'insideStart') {
    labelAngle = startAngle + sign * offset;
    direction = clockWise;
  } else if (position === 'insideEnd') {
    labelAngle = endAngle - sign * offset;
    direction = !clockWise;
  } else if (position === 'end') {
    labelAngle = endAngle + sign * offset;
    direction = clockWise;
  }

  direction = deltaAngle <= 0 ? direction : !direction;

  const startPoint = polarToCartesian(cx, cy, radius, labelAngle);
  const endPoint = polarToCartesian(cx, cy, radius, labelAngle + (direction ? 1 : -1) * 359);
  const path = `M${startPoint.x},${startPoint.y}
    A${radius},${radius},0,1,${direction ? 0 : 1},
    ${endPoint.x},${endPoint.y}`;
  const id = _.isNil(labelProps.id) ? uniqueId('recharts-radial-line-') : labelProps.id;

  return (
    <text
      {...attrs}
      dominantBaseline="central"
      className={classNames('recharts-radial-bar-label', className)}
    >
      <defs><path id={id} d={path} /></defs>
      <textPath xlinkHref={`#${id}`}>{label}</textPath>
    </text>
  );
};

const getAttrsOfPolarLabel = (props) => {
  const { viewBox, offset, position } = props;
  const { cx, cy, innerRadius, outerRadius, startAngle, endAngle } = viewBox;
  const midAngle = (startAngle + endAngle) / 2;

  if (position === 'outside') {
    const { x, y } = polarToCartesian(cx, cy, outerRadius + offset, midAngle);

    return {
      x,
      y,
      textAnchor: x >= cx ? 'start' : 'end',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'center') {
    return {
      x: cx,
      y: cy,
      textAnchor: 'middle',
      verticalAnchor: 'middle',
    };
  }

  const r = (innerRadius + outerRadius) / 2;
  const { x, y } = polarToCartesian(cx, cy, r, midAngle);

  return {
    x,
    y,
    textAnchor: 'middle',
    verticalAnchor: 'middle',
  };
};

const getAttrsOfCartesianLabel = (props) => {
  const { viewBox, offset, position } = props;
  const { x, y, width, height } = viewBox;
  const sign = height >= 0 ? 1 : -1;

  if (position === 'top') {
    return {
      x: x + width / 2,
      y: y - sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'end',
    };
  }

  if (position === 'bottom') {
    return {
      x: x + width / 2,
      y: y + height + sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'start',
    };
  }

  if (position === 'left') {
    return {
      x: x - offset,
      y: y + height / 2,
      textAnchor: 'end',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'right') {
    return {
      x: x + width + offset,
      y: y + height / 2,
      textAnchor: 'start',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'insideLeft') {
    return {
      x: x + offset,
      y: y + height / 2,
      textAnchor: 'start',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'insideRight') {
    return {
      x: x + width - offset,
      y: y + height / 2,
      textAnchor: 'end',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'insideTop') {
    return {
      x: x + width / 2,
      y: y + sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'start',
    };
  }

  if (position === 'insideBottom') {
    return {
      x: x + width / 2,
      y: y + height - sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'end',
    };
  }

  if (position === 'insideTopLeft') {
    return {
      x: x + offset,
      y: y + sign * offset,
      textAnchor: 'start',
      verticalAnchor: 'start',
    };
  }

  if (position === 'insideTopRight') {
    return {
      x: x + width - offset,
      y: y + sign * offset,
      textAnchor: 'end',
      verticalAnchor: 'start',
    };
  }

  if (position === 'insideBottomLeft') {
    return {
      x: x + offset,
      y: y + height - sign * offset,
      textAnchor: 'start',
      verticalAnchor: 'end',
    };
  }

  if (position === 'insideBottomRight') {
    return {
      x: x + width - offset,
      y: y + height - sign * offset,
      textAnchor: 'end',
      verticalAnchor: 'end',
    };
  }

  if (_.isObject(position) && (isNumber(position.x) || isPercent(position.x)) &&
    (isNumber(position.y) || isPercent(position.y))
  ) {
    return {
      x: x + getPercentValue(position.x, width),
      y: y + getPercentValue(position.y, height),
      textAnchor: 'end',
      verticalAnchor: 'end',
    };
  }

  return {
    x: x + width / 2,
    y: y + height / 2,
    textAnchor: 'middle',
    verticalAnchor: 'middle',
  };
};

const isPolar = viewBox => isNumber(viewBox.cx);

function Label(props) {
  const { viewBox, position, value, children, content, className = '' } = props;

  if (!viewBox || (_.isNil(value) && _.isNil(children) &&
    !isValidElement(content) && !_.isFunction(content))) { return null; }

  if (isValidElement(content)) { return cloneElement(content, props); }

  let label;
  if (_.isFunction(content)) {
    label = content(props);

    if (isValidElement(label)) {
      return label;
    }
  } else {
    label = getLabel(props);
  }

  const isPolarLabel = isPolar(viewBox);
  const attrs = getPresentationAttributes(props);

  if (isPolarLabel && (position === 'insideStart' ||
    position === 'insideEnd' || position === 'end')) {
    return renderRadialLabel(props, label, attrs);
  }

  const positionAttrs = isPolarLabel ?
    getAttrsOfPolarLabel(props) :
    getAttrsOfCartesianLabel(props);

  return (
    <Text
      className={classNames('recharts-label', className)}
      {...attrs}
      {...positionAttrs}
    >
      {label}
    </Text>
  );
}

Label.displayName = 'Label';
Label.defaultProps = defaultProps;
Label.propTypes = propTypes;

const parseViewBox = (props) => {
  const { cx, cy, angle, startAngle, endAngle, r, radius, innerRadius, outerRadius,
    x, y, top, left, width, height, clockWise } = props;

  if (isNumber(width) && isNumber(height)) {
    if (isNumber(x) && isNumber(y)) {
      return { x, y, width, height };
    } else if (isNumber(top) && isNumber(left)) {
      return { x: top, y: left, width, height };
    }
  }

  if (isNumber(x) && isNumber(y)) {
    return { x, y, width: 0, height: 0 };
  }

  if (isNumber(cx) && isNumber(cy)) {
    return {
      cx, cy,
      startAngle: startAngle || angle || 0,
      endAngle: endAngle || angle || 0,
      innerRadius: innerRadius || 0,
      outerRadius: outerRadius || radius || r || 0,
      clockWise,
    };
  }

  if (props.viewBox) {
    return props.viewBox;
  }

  return {};
};

const parseLabel = (label, viewBox) => {
  if (!label) { return null; }

  if (label === true) {
    return <Label key="label-implicit" viewBox={viewBox} />;
  }

  if (isNumOrStr(label)) {
    return <Label key="label-implicit" viewBox={viewBox} value={label} />;
  }

  if (isValidElement(label) || _.isFunction(label)) {
    return <Label key="label-implicit" content={label} viewBox={viewBox} />;
  }

  if (_.isObject(label)) {
    return <Label viewBox={viewBox} {...label} key="label-implicit" />;
  }

  return null;
};

const renderCallByParent = (parentProps, viewBox, ckeckPropsLabel = true) => {
  if (!parentProps || (!parentProps.children && (ckeckPropsLabel && !parentProps.label))) {
    return null;
  }
  const { children } = parentProps;
  const parentViewBox = parseViewBox(parentProps);

  const explicitChilren = findAllByType(children, Label).map((child, index) =>
    cloneElement(child, {
      viewBox: viewBox || parentViewBox,
      key: `label-${index}`,
    })
  );

  if (!ckeckPropsLabel) { return explicitChilren; }
  const implicitLabel = parseLabel(parentProps.label, viewBox || parentViewBox);

  return [implicitLabel, ...explicitChilren];
};

Label.parseViewBox = parseViewBox;
Label.renderCallByParent = renderCallByParent;

export default Label;
<commit_msg>Position the 'top' label outside the element for negative heights<commit_after>import React, { cloneElement, isValidElement } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import classNames from 'classnames';
import Text from './Text';
import { getPresentationAttributes, findAllByType } from '../util/ReactUtils';
import { isNumOrStr, isNumber, isPercent, getPercentValue, uniqueId,
  mathSign } from '../util/DataUtils';
import { polarToCartesian } from '../util/PolarUtils';

const cartesianViewBoxShape = PropTypes.shape({
  x: PropTypes.number,
  y: PropTypes.number,
  width: PropTypes.number,
  height: PropTypes.number,
});
const polarViewBoxShape = PropTypes.shape({
  cx: PropTypes.number,
  cy: PropTypes.number,
  innerRadius: PropTypes.number,
  outerRadius: PropTypes.number,
  startAngle: PropTypes.number,
  endAngle: PropTypes.number,
});

const propTypes = {
  viewBox: PropTypes.oneOfType([cartesianViewBoxShape, polarViewBoxShape]),
  formatter: PropTypes.func,
  value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  offset: PropTypes.number,
  position: PropTypes.oneOf([
    'top', 'left', 'right', 'bottom', 'inside', 'outside',
    'insideLeft', 'insideRight', 'insideTop', 'insideBottom',
    'insideTopLeft', 'insideBottomLeft', 'insideTopRight', 'insideBottomRight',
    'insideStart', 'insideEnd', 'end', 'center',
  ]),
  children: PropTypes.oneOfType([
    PropTypes.arrayOf(PropTypes.node),
    PropTypes.node,
  ]),
  className: PropTypes.string,
  content: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
};

const defaultProps = {
  offset: 5,
};

const getLabel = (props) => {
  const { value, formatter } = props;
  const label = _.isNil(props.children) ? value : props.children;

  if (_.isFunction(formatter)) {
    return formatter(label);
  }

  return label;
};

const getDeltaAngle = (startAngle, endAngle) => {
  const sign = mathSign(endAngle - startAngle);
  const deltaAngle = Math.min(Math.abs(endAngle - startAngle), 360);

  return sign * deltaAngle;
};

const renderRadialLabel = (labelProps, label, attrs) => {
  const { position, viewBox, offset, className } = labelProps;
  const { cx, cy, innerRadius, outerRadius, startAngle, endAngle,
    clockWise } = viewBox;
  const radius = (innerRadius + outerRadius) / 2;
  const deltaAngle = getDeltaAngle(startAngle, endAngle);
  const sign = deltaAngle >= 0 ? 1 : -1;
  let labelAngle, direction;

  if (position === 'insideStart') {
    labelAngle = startAngle + sign * offset;
    direction = clockWise;
  } else if (position === 'insideEnd') {
    labelAngle = endAngle - sign * offset;
    direction = !clockWise;
  } else if (position === 'end') {
    labelAngle = endAngle + sign * offset;
    direction = clockWise;
  }

  direction = deltaAngle <= 0 ? direction : !direction;

  const startPoint = polarToCartesian(cx, cy, radius, labelAngle);
  const endPoint = polarToCartesian(cx, cy, radius, labelAngle + (direction ? 1 : -1) * 359);
  const path = `M${startPoint.x},${startPoint.y}
    A${radius},${radius},0,1,${direction ? 0 : 1},
    ${endPoint.x},${endPoint.y}`;
  const id = _.isNil(labelProps.id) ? uniqueId('recharts-radial-line-') : labelProps.id;

  return (
    <text
      {...attrs}
      dominantBaseline="central"
      className={classNames('recharts-radial-bar-label', className)}
    >
      <defs><path id={id} d={path} /></defs>
      <textPath xlinkHref={`#${id}`}>{label}</textPath>
    </text>
  );
};

const getAttrsOfPolarLabel = (props) => {
  const { viewBox, offset, position } = props;
  const { cx, cy, innerRadius, outerRadius, startAngle, endAngle } = viewBox;
  const midAngle = (startAngle + endAngle) / 2;

  if (position === 'outside') {
    const { x, y } = polarToCartesian(cx, cy, outerRadius + offset, midAngle);

    return {
      x,
      y,
      textAnchor: x >= cx ? 'start' : 'end',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'center') {
    return {
      x: cx,
      y: cy,
      textAnchor: 'middle',
      verticalAnchor: 'middle',
    };
  }

  const r = (innerRadius + outerRadius) / 2;
  const { x, y } = polarToCartesian(cx, cy, r, midAngle);

  return {
    x,
    y,
    textAnchor: 'middle',
    verticalAnchor: 'middle',
  };
};

const getAttrsOfCartesianLabel = (props) => {
  const { viewBox, offset, position } = props;
  const { x, y, width, height } = viewBox;
  const sign = height >= 0 ? 1 : -1;

  if (position === 'top') {
    return {
      x: x + width / 2,
      y: y - sign * offset,
      textAnchor: 'middle',
      verticalAnchor: sign > 0 ? 'end' : 'start',
    };
  }

  if (position === 'bottom') {
    return {
      x: x + width / 2,
      y: y + height + sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'start',
    };
  }

  if (position === 'left') {
    return {
      x: x - offset,
      y: y + height / 2,
      textAnchor: 'end',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'right') {
    return {
      x: x + width + offset,
      y: y + height / 2,
      textAnchor: 'start',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'insideLeft') {
    return {
      x: x + offset,
      y: y + height / 2,
      textAnchor: 'start',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'insideRight') {
    return {
      x: x + width - offset,
      y: y + height / 2,
      textAnchor: 'end',
      verticalAnchor: 'middle',
    };
  }

  if (position === 'insideTop') {
    return {
      x: x + width / 2,
      y: y + sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'start',
    };
  }

  if (position === 'insideBottom') {
    return {
      x: x + width / 2,
      y: y + height - sign * offset,
      textAnchor: 'middle',
      verticalAnchor: 'end',
    };
  }

  if (position === 'insideTopLeft') {
    return {
      x: x + offset,
      y: y + sign * offset,
      textAnchor: 'start',
      verticalAnchor: 'start',
    };
  }

  if (position === 'insideTopRight') {
    return {
      x: x + width - offset,
      y: y + sign * offset,
      textAnchor: 'end',
      verticalAnchor: 'start',
    };
  }

  if (position === 'insideBottomLeft') {
    return {
      x: x + offset,
      y: y + height - sign * offset,
      textAnchor: 'start',
      verticalAnchor: 'end',
    };
  }

  if (position === 'insideBottomRight') {
    return {
      x: x + width - offset,
      y: y + height - sign * offset,
      textAnchor: 'end',
      verticalAnchor: 'end',
    };
  }

  if (_.isObject(position) && (isNumber(position.x) || isPercent(position.x)) &&
    (isNumber(position.y) || isPercent(position.y))
  ) {
    return {
      x: x + getPercentValue(position.x, width),
      y: y + getPercentValue(position.y, height),
      textAnchor: 'end',
      verticalAnchor: 'end',
    };
  }

  return {
    x: x + width / 2,
    y: y + height / 2,
    textAnchor: 'middle',
    verticalAnchor: 'middle',
  };
};

const isPolar = viewBox => isNumber(viewBox.cx);

function Label(props) {
  const { viewBox, position, value, children, content, className = '' } = props;

  if (!viewBox || (_.isNil(value) && _.isNil(children) &&
    !isValidElement(content) && !_.isFunction(content))) { return null; }

  if (isValidElement(content)) { return cloneElement(content, props); }

  let label;
  if (_.isFunction(content)) {
    label = content(props);

    if (isValidElement(label)) {
      return label;
    }
  } else {
    label = getLabel(props);
  }

  const isPolarLabel = isPolar(viewBox);
  const attrs = getPresentationAttributes(props);

  if (isPolarLabel && (position === 'insideStart' ||
    position === 'insideEnd' || position === 'end')) {
    return renderRadialLabel(props, label, attrs);
  }

  const positionAttrs = isPolarLabel ?
    getAttrsOfPolarLabel(props) :
    getAttrsOfCartesianLabel(props);

  return (
    <Text
      className={classNames('recharts-label', className)}
      {...attrs}
      {...positionAttrs}
    >
      {label}
    </Text>
  );
}

Label.displayName = 'Label';
Label.defaultProps = defaultProps;
Label.propTypes = propTypes;

const parseViewBox = (props) => {
  const { cx, cy, angle, startAngle, endAngle, r, radius, innerRadius, outerRadius,
    x, y, top, left, width, height, clockWise } = props;

  if (isNumber(width) && isNumber(height)) {
    if (isNumber(x) && isNumber(y)) {
      return { x, y, width, height };
    } else if (isNumber(top) && isNumber(left)) {
      return { x: top, y: left, width, height };
    }
  }

  if (isNumber(x) && isNumber(y)) {
    return { x, y, width: 0, height: 0 };
  }

  if (isNumber(cx) && isNumber(cy)) {
    return {
      cx, cy,
      startAngle: startAngle || angle || 0,
      endAngle: endAngle || angle || 0,
      innerRadius: innerRadius || 0,
      outerRadius: outerRadius || radius || r || 0,
      clockWise,
    };
  }

  if (props.viewBox) {
    return props.viewBox;
  }

  return {};
};

const parseLabel = (label, viewBox) => {
  if (!label) { return null; }

  if (label === true) {
    return <Label key="label-implicit" viewBox={viewBox} />;
  }

  if (isNumOrStr(label)) {
    return <Label key="label-implicit" viewBox={viewBox} value={label} />;
  }

  if (isValidElement(label) || _.isFunction(label)) {
    return <Label key="label-implicit" content={label} viewBox={viewBox} />;
  }

  if (_.isObject(label)) {
    return <Label viewBox={viewBox} {...label} key="label-implicit" />;
  }

  return null;
};

const renderCallByParent = (parentProps, viewBox, ckeckPropsLabel = true) => {
  if (!parentProps || (!parentProps.children && (ckeckPropsLabel && !parentProps.label))) {
    return null;
  }
  const { children } = parentProps;
  const parentViewBox = parseViewBox(parentProps);

  const explicitChilren = findAllByType(children, Label).map((child, index) =>
    cloneElement(child, {
      viewBox: viewBox || parentViewBox,
      key: `label-${index}`,
    })
  );

  if (!ckeckPropsLabel) { return explicitChilren; }
  const implicitLabel = parseLabel(parentProps.label, viewBox || parentViewBox);

  return [implicitLabel, ...explicitChilren];
};

Label.parseViewBox = parseViewBox;
Label.renderCallByParent = renderCallByParent;

export default Label;
<|endoftext|>


<commit_before>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { warpValue } from "../helpers";

export const title = "Day 13: Knights of the Dinner Table";

function getGuests(list) {
    const guests = _.compose(_.unique, _.map(_.get("guest")));

    return _.reduce((map, guest, index) => {
        map[index] = guest;

        return map;
    }, {}, guests(list));
}

export function getSeating(guestIDs, subSeating = [], result = []) {
    if (!guestIDs.length)
        return result.concat([subSeating]);

    // Loop through each ID and call getSeating with current value + rest.
    return _.reduce((seating, guestID) => {
        const rest = _.filter((id) => id !== guestID, guestIDs);

        // Append any complete seating arrangements to the returned value.
        return seating.concat(getSeating(rest, subSeating.concat(guestID), result));
    }, result, guestIDs);
}

export function parse(input) {
    return _.map((sentence) => {
        const [guest, neighbour] = sentence.match(/[A-Z]{1}[a-z]+/g);
        const [adjustment, value] = sentence.match(/(gain|lose)|(\d+)/g);
        const happiness = value * (adjustment == "gain" ? 1 : -1);

        return {
            guest,
            neighbour,
            value: happiness
        };
    }, input);
}

export function calculateSeating(list) {
    const guests = getGuests(list);
    const guestIDs = _.map((x) => parseInt(x, 10), Object.keys(guests));
    const arrangements = getSeating(guestIDs);

    // Calculate the total cost of each arrangement.
    const cost = _.map((arrangement) => {
        return _.reduce((total, guest, index) => {
            // Get an array of people (left, current, and right).
            const [left, current, right] = _.map((offset) => {
                return guests[arrangement[warpValue(index + offset, 4)]];
            }, [-1, 0, 1]);
            // Looks through the list to find the matching guest and neighbour.
            const findInList = _.curry((x, a, b) => {
                return _.filter(({guest, neighbour}) => guest == a && neighbour == b, x);
            });
            const getValue = _.compose(_.get("value"), _.first, findInList(list, current));

            return total + (getValue(left) + getValue(right));
        }, 0, arrangement);
    }, arrangements);

    return _.last(cost.sort());
}

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim().split("\n");

    console.log("What is the total change in happiness for the optimal seating arrangement?");
}
<commit_msg>Fixed a couple bugs with calculateSeating. Completed part 1. - Sorting, by default, assumed strings, not numbers. - warpValue was passed the wrong max value. - Spoiler alert: the answer is 733.<commit_after>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { warpValue } from "../helpers";

export const title = "Day 13: Knights of the Dinner Table";

function getGuests(list) {
    const guests = _.compose(_.unique, _.map(_.get("guest")));

    return _.reduce((map, guest, index) => {
        map[index] = guest;

        return map;
    }, {}, guests(list));
}

export function getSeating(guestIDs, subSeating = [], result = []) {
    if (!guestIDs.length)
        return result.concat([subSeating]);

    // Loop through each ID and call getSeating with current value + rest.
    return _.reduce((seating, guestID) => {
        const rest = _.filter((id) => id !== guestID, guestIDs);

        // Append any complete seating arrangements to the returned value.
        return seating.concat(getSeating(rest, subSeating.concat(guestID), result));
    }, result, guestIDs);
}

export function parse(input) {
    return _.map((sentence) => {
        const [guest, neighbour] = sentence.match(/[A-Z]{1}[a-z]+/g);
        const [adjustment, value] = sentence.match(/(gain|lose)|(\d+)/g);
        const happiness = value * (adjustment == "gain" ? 1 : -1);

        return {
            guest,
            neighbour,
            value: happiness
        };
    }, input);
}

export function calculateSeating(list) {
    const guests = getGuests(list);
    const guestIDs = _.map((x) => parseInt(x, 10), Object.keys(guests));
    const arrangements = getSeating(guestIDs);

    // Calculate the total cost of each arrangement.
    const cost = _.map((arrangement) => {
        return _.reduce((total, guest, index) => {
            // Get an array of people (left, current, and right).
            const [left, current, right] = _.map((offset) => {
                return guests[arrangement[warpValue(index + offset, guestIDs.length)]];
            }, [-1, 0, 1]);
            // Looks through the list to find the matching guest and neighbour.
            const findInList = _.curry((x, a, b) => {
                return _.filter(({guest, neighbour}) => guest == a && neighbour == b, x);
            });
            const getValue = _.compose(_.get("value"), _.first, findInList(list, current));

            return total + (getValue(left) + getValue(right));
        }, 0, arrangement);
    }, arrangements);

    return _.last(cost.sort((a,b) => a - b));
}

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim().split("\n");
    const happinessList = parse(input);

    console.log("What is the total change in happiness for the optimal seating arrangement?", calculateSeating(happinessList));
}
<|endoftext|>


<commit_before>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

(function() {
  "use strict";

  var websocket, outputs = {};

  function onClose() {
    window.alert('websocket connection closed');
  }

  function sendMessage(m) {
    websocket.send(JSON.stringify(m));
  }

  function onMessage(e) {
    var m = JSON.parse(e.data);
    var o = outputs[m.Id];
    if (o === null) {
      return;
    }
    if (m.Kind === "stdout" || m.Kind === "stderr") {
      showMessage(o, m.Body, m.Kind);
    }
    if (m.Kind === "end") {
      var s = "Program exited";
      if (m.Body !== "") {
        s += ": " + m.Body;
      } else {
        s += ".";
      }
      s += "\n";
      showMessage(o, s, "system");
    }
  }

  function showMessage(o, m, className) {
    var span = document.createElement("span");
    var needScroll = (o.scrollTop + o.offsetHeight) == o.scrollHeight;
    m = m.replace(/&/g, "&");
    m = m.replace(/</g, "<");
    span.innerHTML = m;
    span.className = className;
    o.appendChild(span);
    if (needScroll)
        o.scrollTop = o.scrollHeight - o.offsetHeight;
  }

  function run(body, output) {
    var id = output.id;
    outputs[id] = output;
    sendMessage({Id: id, Kind: "run", Body: body});
    return function() {
      sendMessage({Id: id, Kind: "kill"});
    };
  }

  window.connectPlayground = function(addr) {
    websocket = new WebSocket(addr);
    websocket.onmessage = onMessage;
    websocket.onclose = onClose;
    return run;
  };
})();
<commit_msg>go.talks: import socket.js changes from go-tour<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

(function() {
  "use strict";

  var websocket, outputs = {};

  function onClose() {
    window.alert('websocket connection closed');
  }

  function sendMessage(m) {
    websocket.send(JSON.stringify(m));
  }

  function onMessage(e) {
    var m = JSON.parse(e.data);
    var o = outputs[m.Id];
    if (o === null) {
      return;
    }
    if (m.Kind === "stdout" || m.Kind === "stderr") {
      showMessage(o, m.Body, m.Kind);
    }
    if (m.Kind === "end") {
      var s = "Program exited";
      if (m.Body !== "") {
        s += ": " + m.Body;
      } else {
        s += ".";
      }
      s += "\n";
      showMessage(o, s, "system");
    }
  }

  function showMessage(o, m, className) {
    var span = document.createElement("span");
    span.className = className;
    if (m.indexOf("IMAGE:") === 0) {
      var url = "data:image/png;base64," + m.substr(6);
      var img = document.createElement("img");
      img.src = url;
      span.appendChild(img);
    } else {
      m = m.replace(/&/g, "&");
      m = m.replace(/</g, "<");
      span.innerHTML = m;
    }
    var needScroll = (o.scrollTop + o.offsetHeight) == o.scrollHeight;
    o.appendChild(span);
    if (needScroll)
        o.scrollTop = o.scrollHeight - o.offsetHeight;
  }

  function run(body, output) {
    var id = output.id;
    outputs[id] = output;
    sendMessage({Id: id, Kind: "run", Body: body});
    return function() {
      sendMessage({Id: id, Kind: "kill"});
    };
  }

  window.connectPlayground = function(addr) {
    websocket = new WebSocket(addr);
    websocket.onmessage = onMessage;
    websocket.onclose = onClose;
    return run;
  };
})();
<|endoftext|>


<commit_before>define(['mixedice', 'eventmap', './component/manager', './log'], function(mixedice, EventMap, ComponentManager, Log) {
  'use strict';

  /**
   * @module lyria/component
   * @requires eventmap
   * @requires lyria/component/manager
   * @requires lyria/log
   */

  return (function() {

    /**
     * @class
     * @alias module:lyria/component
     * @lends module:eventmap
     */

    var Component = function(name, factory) {
      mixedice([this, Component.prototype], new EventMap());

      this.name = name != null ? name : this.constructor.name;

      this.type = 'Component';

      this.children = {};

      if (factory) {
        factory.apply(this, [this]);
      }

      this.on('update', function(dt) {
        for (var key in children) {
          var value = children[key];
          if (value && value.trigger) {
            value.trigger('update', dt);
          }
        }
      });
    };

    /**
     * Adds a component to this entity
     *
     * @param {Object} child
     */
    Component.prototype.add = function(child) {
      var name = 'anonymous-' + Date.now();
      var childObject = null;

      if (typeof child === 'string') {
        name = child;
        childObject = ComponentManager.get(name);
      } else {
        name = child.name || name;
        childObject = child;
      }

      children[name] = childObject;

      this.trigger('add', name);
      childObject.trigger('added', this);
    };

    /**
     * Removes a component from the entity
     *
     * @param {String} name
     */
    Component.prototype.remove = function(name) {
      if (name == null) {
        return;
      }

      delete children[name];

      this.trigger('remove', name);
    };

    /**
     * Logs from the component itself
     *
     * @param {String} text
     */
    Component.prototype.log = function(text) {
      if (this.type === this.name) {
        Log.i(this.type + ': ' + text);
      } else {
        Log.i('[' + this.type + '] ' + this.name + ': ' + text);
      }
    };

    return Component;

  })();

});
<commit_msg>Fixes reference to children<commit_after>define(['mixedice', 'eventmap', './component/manager', './log'], function(mixedice, EventMap, ComponentManager, Log) {
  'use strict';

  /**
   * @module lyria/component
   * @requires eventmap
   * @requires lyria/component/manager
   * @requires lyria/log
   */

  return (function() {

    /**
     * @class
     * @alias module:lyria/component
     * @lends module:eventmap
     */

    var Component = function(name, factory) {
      mixedice([this, Component.prototype], new EventMap());

      this.name = name != null ? name : this.constructor.name;

      this.type = 'Component';

      this.children = {};

      if (factory) {
        factory.apply(this, [this]);
      }

      this.on('update', function(dt) {
        for (var key in this.children) {
          var value = this.children[key];
          if (value && value.trigger) {
            value.trigger('update', dt);
          }
        }
      });
    };

    /**
     * Adds a component to this entity
     *
     * @param {Object} child
     */
    Component.prototype.add = function(child) {
      var name = 'anonymous-' + Date.now();
      var childObject = null;

      if (typeof child === 'string') {
        name = child;
        childObject = ComponentManager.get(name);
      } else {
        name = child.name || name;
        childObject = child;
      }

      this.children[name] = childObject;

      this.trigger('add', name);
      childObject.trigger('added', this);
    };

    /**
     * Removes a component from the entity
     *
     * @param {String} name
     */
    Component.prototype.remove = function(name) {
      if (name == null) {
        return;
      }

      delete this.children[name];

      this.trigger('remove', name);
    };

    /**
     * Logs from the component itself
     *
     * @param {String} text
     */
    Component.prototype.log = function(text) {
      if (this.type === this.name) {
        Log.i(this.type + ': ' + text);
      } else {
        Log.i('[' + this.type + '] ' + this.name + ': ' + text);
      }
    };

    return Component;

  })();

});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import {merge} from 'lodash';
import Mousetrap from 'mousetrap';

import Actions from '../Actions';
import SprdRange from '../SprdRange';
import SprdNavigator from '../SprdNavigator';
import {DIRECTION} from '../Constants';

export default class Cell extends React.Component {

  static propTypes = {
    row: PropTypes.number,
    col: PropTypes.number,
    value: PropTypes.string,
    selectedRange: PropTypes.array
  };

  constructor(props){
    super(props);
    this.CELL_MODES = {
      INACTIVE: "inactive",
      ACTIVE: "active",
      EDITING: "editing",
      HORIZONTAL_HIGHLIGHT: "horizontal_higlight",
      VERTICAL_HIGHLIGHT: "vertical_higlight"
    };

    this.state = {
      mode: this.CELL_MODES.INACTIVE,
      value: this.props.value
    };

    this.cellClicked = this.cellClicked.bind(this);
    this.cellDoubleClicked = this.cellDoubleClicked.bind(this);
    this.cellKeyDown = this.cellKeyDown.bind(this);
    this.inputValueChanged = this.inputValueChanged.bind(this);
    this.saveInputValue = this.saveInputValue.bind(this);
  }

  componentDidMount(){
    Mousetrap(this.input).bind("enter", () => {
      SprdNavigator.move(this.props.selectedRange, DIRECTION.DOWN);
      this.setState({mode: this.CELL_MODES.INACTIVE});
    });
  }

  componentWillReceiveProps(nextProps){
    let {selectedRange, row, col} = nextProps;
    let {mode} = this.state;
    for(let range of selectedRange){
      if(range.isCellSelected(row, col)){
        this.setState({mode: this.CELL_MODES.ACTIVE});
      } else if(range.isNumberCellSelected(row, col)){
        this.setState({mode: this.CELL_MODES.HORIZONTAL_HIGHLIGHT});
      } else if(range.isHeaderSelected(col)){
        this.setState({mode: this.CELL_MODES.VERTICAL_HIGHLIGHT});
      } else if(mode !== this.CELL_MODES.INACTIVE){
        this.setState({mode: this.CELL_MODES.INACTIVE});
      }
    }
  }

  shouldComponentUpdate(nextProps, nextState){
    if(nextState.value !== this.state.value) return true;
    return nextState.mode !== this.state.mode;
  }

  //this called twice when cell is double clicked
  //to prevent that we check if the current cell is already selected
  cellClicked(){
    let {row, col, selectedRange} = this.props;
    let thisCellSelected = false;
    for(let range of selectedRange){
      thisCellSelected =  range.isCellSelected(row, col);
      if(thisCellSelected) break;
    }
    if(!thisCellSelected)
      Actions.selectRange(new SprdRange(row, col, row, col));
  }

  cellDoubleClicked(){
    this.setState({mode: this.CELL_MODES.EDITING}, () => {
      this.input.focus();
    })
  }

  cellKeyDown(){
    this.setState({mode: this.CELL_MODES.EDITING}, () => {
      this.input.focus();
    });
  }

  currentStyle(){
    switch(this.state.mode){
      case this.CELL_MODES.INACTIVE:
        return styles.td_inactive;
      case this.CELL_MODES.ACTIVE:
        return styles.td_active;
      case this.CELL_MODES.EDITING:
        return styles.td_editing;
      case this.CELL_MODES.HORIZONTAL_HIGHLIGHT:
        return styles.td_horizontal_highlight;
      case this.CELL_MODES.VERTICAL_HIGHLIGHT:
        return styles.td_vertical_highlight;
    }
  }

  inputValueChanged(e){
    this.setState({value: e.target.value});
  }

  saveInputValue(){
    let {row, col} = this.props;
    Actions.setValue(this.state.value, new SprdRange(row, col, row, col));
  }

  renderInnerCell(){
    let {mode, value} = this.state;
    return (
      <input
        onChange={this.inputValueChanged}
        hidden={mode !== this.CELL_MODES.EDITING}
        type='text'
        onBlur={this.saveInputValue}
        value={value}
        ref={(input) => { this.input = input; }}
        style={styles.input_active}/>
    );
  }

  renderOuterCell(){
    let {mode, value} = this.state;
    return (
      <div
        hidden={mode === this.CELL_MODES.EDITING}
        style={styles.outer_cell}>
          {this.state.value}
      </div>
    );
  }

  render(){
    console.log("cell re-render");
    return (
      <td
        onDoubleClick={this.cellDoubleClicked}
        onKeyDown={this.cellKeyDown}
        onClick={this.cellClicked}
        style={this.currentStyle()}>
          {[this.renderInnerCell(), this.renderOuterCell()]}
      </td>
    )
  }
}

const styles = {
  input_active: {
    border: "none",
    borderStyle: "none",
    width: "95%",
    padding: 0,
    margin: 0,
    zIndex: 100,
    fontSize:14,
    textAlign: "right"
  },
  td_horizontal_highlight: {
    borderTop: "1px solid #2196F3",
    borderBottom: "1px solid #2196F3",
    borderLeft: "1px solid #BDBDBD",
    borderRight: "1px solid #BDBDBD",
    background: "rgba(33, 150, 243, 0.1)"
  },
  td_vertical_highlight: {
    borderLeft: "1px solid #2196F3",
    borderRight: "1px solid #2196F3",
    borderTop: "1px solid #BDBDBD",
    borderBottom: "1px solid #BDBDBD",
    background: "rgba(33, 150, 243, 0.1)"
  },
  td_active: {
    border: "2px solid #2196F3",
  },
  td_inactive: {
    borderTop: "1px solid #BDBDBD",
    borderLeft: "1px solid #BDBDBD",
  },
  td_editing: {
   border: "2px solid #2196F3"
  },
  outer_cell: {
    fontSize: 14,
    textAlign: "right"
  }
}<commit_msg>maybe change cell mode<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import {merge} from 'lodash';
import Mousetrap from 'mousetrap';

import Actions from '../Actions';
import SprdRange from '../SprdRange';
import SprdNavigator from '../SprdNavigator';
import {DIRECTION} from '../Constants';

export default class Cell extends React.Component {

  static propTypes = {
    row: PropTypes.number,
    col: PropTypes.number,
    value: PropTypes.string,
    selectedRange: PropTypes.array
  };

  constructor(props){
    super(props);
    this.CELL_MODES = {
      INACTIVE: "inactive",
      ACTIVE: "active",
      EDITING: "editing",
      HORIZONTAL_HIGHLIGHT: "horizontal_higlight",
      VERTICAL_HIGHLIGHT: "vertical_higlight"
    };

    this.state = {
      mode: this.CELL_MODES.INACTIVE,
      value: this.props.value
    };

    this.cellClicked = this.cellClicked.bind(this);
    this.cellDoubleClicked = this.cellDoubleClicked.bind(this);
    this.inputValueChanged = this.inputValueChanged.bind(this);
    this.saveInputValue = this.saveInputValue.bind(this);
  }

  componentDidMount(){
    Mousetrap(this.input).bind("enter", () => {
      this.setState({mode: this.CELL_MODES.INACTIVE});
      SprdNavigator.move(this.props.selectedRange, DIRECTION.DOWN);
    });
  }

  componentWillReceiveProps(nextProps){
    this.maybeChangeCellMode(nextProps);
  }

  shouldComponentUpdate(nextProps, nextState){
    if(nextState.value !== this.state.value) return true;
    return nextState.mode !== this.state.mode;
  }

  maybeChangeCellMode(props){
    let {mode} = this.state;
    let {selectedRange, row, col} = props;
    for(let range of selectedRange){
      if(range.isCellSelected(row, col)){
        this.setState({mode: this.CELL_MODES.ACTIVE});
      } else if(range.isNumberCellSelected(row, col)){
        this.setState({mode: this.CELL_MODES.HORIZONTAL_HIGHLIGHT});
      } else if(range.isHeaderSelected(col)){
        this.setState({mode: this.CELL_MODES.VERTICAL_HIGHLIGHT});
      } else if(mode !== this.CELL_MODES.INACTIVE){
        this.setState({mode: this.CELL_MODES.INACTIVE});
      }
    }
  }

  //this called twice when cell is double clicked
  //to prevent that we check if the current cell is already selected
  cellClicked(){
    let {row, col, selectedRange} = this.props;
    let thisCellSelected = false;
    for(let range of selectedRange){
      thisCellSelected =  range.isCellSelected(row, col);
      if(thisCellSelected) break;
    }
    if(!thisCellSelected)
      Actions.selectRange(new SprdRange(row, col, row, col));
  }

  cellDoubleClicked(){
    this.setState({mode: this.CELL_MODES.EDITING}, () => {
      this.input.focus();
    })
  }

  currentStyle(){
    switch(this.state.mode){
      case this.CELL_MODES.INACTIVE:
        return styles.td_inactive;
      case this.CELL_MODES.ACTIVE:
        return styles.td_active;
      case this.CELL_MODES.EDITING:
        return styles.td_editing;
      case this.CELL_MODES.HORIZONTAL_HIGHLIGHT:
        return styles.td_horizontal_highlight;
      case this.CELL_MODES.VERTICAL_HIGHLIGHT:
        return styles.td_vertical_highlight;
    }
  }

  inputValueChanged(e){
    this.setState({value: e.target.value});
  }

  saveInputValue(){
    let {row, col} = this.props;
    Actions.setValue(this.state.value, new SprdRange(row, col, row, col));
  }

  renderInnerCell(){
    let {mode, value} = this.state;
    return (
      <input
        onChange={this.inputValueChanged}
        hidden={mode !== this.CELL_MODES.EDITING}
        type='text'
        onBlur={this.saveInputValue}
        value={value}
        ref={(input) => { this.input = input; }}
        style={styles.input_active}/>
    );
  }

  renderOuterCell(){
    let {mode, value} = this.state;
    return (
      <div
        hidden={mode === this.CELL_MODES.EDITING}
        style={styles.outer_cell}>
          {this.state.value}
      </div>
    );
  }

  render(){
    console.log("cell re-render");
    return (
      <td
        onDoubleClick={this.cellDoubleClicked}
        onClick={this.cellClicked}
        style={this.currentStyle()}>
          {[this.renderInnerCell(), this.renderOuterCell()]}
      </td>
    )
  }
}

const styles = {
  input_active: {
    border: "none",
    borderStyle: "none",
    width: "95%",
    padding: 0,
    margin: 0,
    zIndex: 100,
    fontSize:14,
    textAlign: "right"
  },
  td_horizontal_highlight: {
    borderTop: "1px solid #2196F3",
    borderBottom: "1px solid #2196F3",
    borderLeft: "1px solid #BDBDBD",
    borderRight: "1px solid #BDBDBD",
    background: "rgba(33, 150, 243, 0.1)"
  },
  td_vertical_highlight: {
    borderLeft: "1px solid #2196F3",
    borderRight: "1px solid #2196F3",
    borderTop: "1px solid #BDBDBD",
    borderBottom: "1px solid #BDBDBD",
    background: "rgba(33, 150, 243, 0.1)"
  },
  td_active: {
    border: "2px solid #2196F3",
  },
  td_inactive: {
    borderTop: "1px solid #BDBDBD",
    borderLeft: "1px solid #BDBDBD",
  },
  td_editing: {
   border: "2px solid #2196F3"
  },
  outer_cell: {
    fontSize: 14,
    textAlign: "right"
  }
}<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { Map, ScaleControl } from 'react-leaflet';
import { connect } from 'react-redux';

import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';
import Panel from 'react-bootstrap/lib/Panel';

import Layers from 'fm3/components/Layers';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import Toasts from 'fm3/components/Toasts';

import SearchMenu from 'fm3/components/SearchMenu';
import SearchResults from 'fm3/components/SearchResults';

import ObjectsMenu from 'fm3/components/ObjectsMenu';
import ObjectsResult from 'fm3/components/ObjectsResult';

import MeasurementMenu from 'fm3/components/MeasurementMenu';
import DistanceMeasurementResult from 'fm3/components/DistanceMeasurementResult';
import AreaMeasurementResult from 'fm3/components/AreaMeasurementResult';
import ElevationMeasurementResult from 'fm3/components/ElevationMeasurementResult';
import LocationResult from 'fm3/components/LocationResult';

import RoutePlannerMenu from 'fm3/components/RoutePlannerMenu';
import RoutePlannerResult from 'fm3/components/RoutePlannerResult';

import TrackViewerMenu from 'fm3/components/TrackViewerMenu';
import TrackViewerResult from 'fm3/components/TrackViewerResult';

import GalleryMenu from 'fm3/components/GalleryMenu';
import GalleryResult from 'fm3/components/GalleryResult';
import GalleryPicker from 'fm3/components/GalleryPicker';

import Settings from 'fm3/components/Settings';

import OpenInExternalAppMenuButton from 'fm3/components/OpenInExternalAppMenuButton';
import ToolsMenuButton from 'fm3/components/ToolsMenuButton';
import MapSwitchButton from 'fm3/components/MapSwitchButton';
import MoreMenuButton from 'fm3/components/MoreMenuButton';

import AsyncElevationChart from 'fm3/components/AsyncElevationChart';

import InfoPointMenu from 'fm3/components/InfoPointMenu';
import InfoPoint from 'fm3/components/InfoPoint';

import ChangesetsMenu from 'fm3/components/ChangesetsMenu';
import Changesets from 'fm3/components/Changesets';

import MapDetailsMenu from 'fm3/components/MapDetailsMenu';
import MapDetails from 'fm3/components/MapDetails';

import ShareMapModal from 'fm3/components/ShareMapModal';
import EmbedMapModal from 'fm3/components/EmbedMapModal';
import LoginModal from 'fm3/components/LoginModal';

import * as FmPropTypes from 'fm3/propTypes';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';

import { mapRefocus, mapReset } from 'fm3/actions/mapActions';
import { setTool, setLocation, exportGpx, clearMap, toggleLocate } from 'fm3/actions/mainActions';
import { authCheckLogin } from 'fm3/actions/authActions';

import { setMapLeafletElement } from 'fm3/leafletElementHolder';

import 'fm3/styles/main.scss';
import 'leaflet/dist/leaflet.css';

class Main extends React.Component {
  static propTypes = {
    lat: PropTypes.number.isRequired,
    lon: PropTypes.number.isRequired,
    zoom: PropTypes.number.isRequired,
    tool: FmPropTypes.tool,
    mapType: FmPropTypes.mapType.isRequired,
    onToolSet: PropTypes.func.isRequired,
    onMapRefocus: PropTypes.func.isRequired,
    activeModal: PropTypes.string,
    progress: PropTypes.bool.isRequired,
    onLocationSet: PropTypes.func.isRequired,
    mouseCursor: PropTypes.string.isRequired,
    embeddedMode: PropTypes.bool.isRequired,
    onCheckLogin: PropTypes.func.isRequired,
    ignoreEscape: PropTypes.bool.isRequired,
    showElevationChart: PropTypes.bool.isRequired,
    showGalleryPicker: PropTypes.bool.isRequired,
    onGpxExport: PropTypes.func.isRequired,
    onMapClear: PropTypes.func.isRequired,
    onLocate: PropTypes.func.isRequired,
    locate: PropTypes.bool.isRequired,
    showLoginModal: PropTypes.bool,
    onMapReset: PropTypes.func.isRequired,
  };

  componentWillMount() {
    this.props.onCheckLogin();
  }

  componentDidMount() {
    setMapLeafletElement(this.map.leafletElement);
    document.addEventListener('keydown', (event) => {
      if (event.keyCode === 27 /* escape key */ && !this.props.ignoreEscape) {
        this.props.onToolSet(null);
      }
    });

    if (this.props.embeddedMode) {
      document.body.classList.add('embedded');
    }

    document.addEventListener('fullscreenchange', this.handleFullscreenChange);
  }

  componentWillUnmount() {
    document.removeEventListener('fullscreenchange', this.handleFullscreenChange);
    setMapLeafletElement(null);
  }

  handleFullscreenChange = () => {
    this.forceUpdate();
  }

  handleMapMoveEnd = () => {
    // TODO analyze why this can be null
    if (!this.map) {
      return;
    }

    const map = this.map.leafletElement;
    const { lat, lng: lon } = map.getCenter();
    const zoom = map.getZoom();

    if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== zoom) {
      this.props.onMapRefocus({ lat, lon, zoom });
    }
  }

  handleLocationFound = (e) => {
    this.props.onLocationSet(e.latitude, e.longitude, e.accuracy);
  }

  openFreemapInNonEmbedMode = () => {
    const currentURL = window.location.href;
    window.open(currentURL.replace('&embed=true', ''), '_blank');
  }

  handleToolCloseClick = () => {
    this.props.onToolSet(null);
  }

  handleFullscreenClick = () => {
    if (document.fullscreenElement) {
      document.exitFullscreen();
    } else {
      document.body.requestFullscreen();
    }
  }

  handleZoomInClick = () => {
    const zoom = this.map.leafletElement.getZoom() + 1;
    this.props.onMapRefocus({ zoom });
  }

  handleZoomOutClick = () => {
    const zoom = this.map.leafletElement.getZoom() - 1;
    this.props.onMapRefocus({ zoom });
  }

  render() {
    const { lat, lon, zoom, mapType,
      tool, activeModal, progress, mouseCursor, embeddedMode, showElevationChart, showGalleryPicker, onMapClear,
      showLoginModal, onMapReset } = this.props;

    const showDefaultMenu = [null, 'location'].includes(tool);

    return (

        {/* embeddedMode && <button id="freemap-logo" className="embedded" onClick={this.openFreemapInNonEmbedMode} /> */}
        <Toasts />

          <Panel className="fm-toolbar">
            <ButtonToolbar>
              <ButtonGroup>
                <Button id="freemap-logo" className={progress ? 'in-progress' : 'idle'} onClick={onMapReset} />
                <ToolsMenuButton />
                <Button onClick={onMapClear} title="Vyčistiť mapu">
                  <FontAwesomeIcon icon="eraser" />
                </Button>
                <Button onClick={this.props.onLocate} title="Kde som?" active={this.props.locate}>
                  <FontAwesomeIcon icon="dot-circle-o" />
                </Button>
                <Button onClick={this.handleFullscreenClick} title={document.fullscreenElement ? 'Zrušiť zobrazenie na celú obrazovku' : 'Na celú obrazovku'}>
                  <FontAwesomeIcon icon={document.fullscreenElement ? 'compress' : 'expand'} />
                </Button>
                <Button onClick={this.props.onGpxExport} title="Exportovať do GPX">
                  <FontAwesomeIcon icon="share" />
                </Button>
                <OpenInExternalAppMenuButton lat={lat} lon={lon} zoom={zoom} mapType={mapType} />
                <MoreMenuButton />
              </ButtonGroup>
            </ButtonToolbar>
          </Panel>
          {tool &&
            <Panel className="fm-toolbar">
              {tool === 'search' && <SearchMenu />}
              {tool === 'objects' && <ObjectsMenu />}
              {tool === 'route-planner' && <RoutePlannerMenu />}
              {['measure-dist', 'measure-ele', 'measure-area'].includes(tool) && <MeasurementMenu />}
              {tool === 'track-viewer' && <TrackViewerMenu />}
              {tool === 'info-point' && <InfoPointMenu />}
              {tool === 'changesets' && <ChangesetsMenu />}
              {tool === 'gallery' && <GalleryMenu />}
              {tool === 'map-details' && <MapDetailsMenu />}

                {' '}
                <Button onClick={this.handleToolCloseClick} title="Zavrieť nástroj" disabled={!tool}>
                  <FontAwesomeIcon icon="close" /> Zavrieť
                </Button>

            </Panel>
          }

          <Panel className="fm-toolbar">
            <ButtonToolbar>
              <MapSwitchButton />
              <ButtonGroup>
                <Button
                  onClick={this.handleZoomInClick}
                  title="Priblížiť mapu"
                  disabled={this.map && this.props.zoom >= this.map.leafletElement.getMaxZoom()}
                >
                  <FontAwesomeIcon icon="plus" />
                </Button>
                <Button
                  onClick={this.handleZoomOutClick}
                  title="Oddialiť mapu"
                  disabled={this.map && this.props.zoom <= this.map.leafletElement.getMinZoom()}
                >
                  <FontAwesomeIcon icon="minus" />
                </Button>
              </ButtonGroup>
            </ButtonToolbar>
          </Panel>

        {activeModal === 'settings' && <Settings />}
        {activeModal === 'share' && <ShareMapModal />}
        {activeModal === 'embed' && <EmbedMapModal />}
        {showLoginModal && <LoginModal />}
        {/* TODO !embeddedMode && <ProgressIndicator active={progress} /> */}

        <div className={`map-holder active-map-type-${mapType}`}>
          <Map
            zoomControl={false}
            minZoom={8}
            ref={(map) => { this.map = map; }}
            center={L.latLng(lat, lon)}
            zoom={zoom}
            onMoveend={this.handleMapMoveEnd}
            onMousemove={handleMapMouseMove}
            onMouseover={handleMapMouseOver}
            onMouseout={handleMapMouseOut}
            onClick={handleMapClick}
            onLocationfound={this.handleLocationFound}
            style={{ cursor: mouseCursor }}
            maxBounds={[[47.040256, 15.4688], [49.837969, 23.906238]]}
          >
            <ScaleControl imperial={false} position="bottomleft" />
            <Layers />

            {(showDefaultMenu || tool === 'search') && <SearchResults />}

            <ObjectsResult />
            <RoutePlannerResult />
            <DistanceMeasurementResult />
            <ElevationMeasurementResult />
            <AreaMeasurementResult />
            <LocationResult />
            <TrackViewerResult />
            <InfoPoint />

            <Changesets />
            {tool === 'map-details' && <MapDetails />}
            {showElevationChart && <AsyncElevationChart />}
            {showGalleryPicker && <GalleryPicker />}
            <GalleryResult />
          </Map>

    );
  }
}

export default connect(
  state => ({
    lat: state.map.lat,
    lon: state.map.lon,
    zoom: state.map.zoom,
    tool: state.main.tool,
    mapType: state.map.mapType,
    activeModal: state.main.activeModal,
    progress: !!state.main.progress.length,
    mouseCursor: selectMouseCursor(state),
    embeddedMode: state.main.embeddedMode,
    user: state.auth.user,
    ignoreEscape: !!(state.main.activeModal && state.main.activeModal !== 'settings' // TODO settings dialog gets also closed
      || state.gallery.activeImageId),
    showElevationChart: !!state.elevationChart.elevationProfilePoints,
    showGalleryPicker: isShowGalleryPicker(state),
    locate: state.main.locate,
    showLoginModal: state.auth.chooseLoginMethod,
  }),
  dispatch => ({
    onToolSet(tool) {
      dispatch(setTool(tool));
    },
    onMapRefocus(changes) {
      dispatch(mapRefocus(changes));
    },
    onLocationSet(lat, lon, accuracy) {
      dispatch(setLocation(lat, lon, accuracy));
    },
    onCheckLogin() {
      dispatch(authCheckLogin());
    },
    onGpxExport() {
      dispatch(exportGpx());
    },
    onMapClear() {
      dispatch(clearMap());
    },
    onMapReset() {
      dispatch(mapReset());
    },
    onLocate() {
      dispatch(toggleLocate());
    },
  }),
)(Main);

function handleMapClick({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mapClick', lat, lon);
}

function handleMapMouseMove({ latlng: { lat, lng: lon }, originalEvent }) {
  mapEventEmitter.emit('mouseMove', lat, lon, originalEvent);
}

function handleMapMouseOver({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOver', lat, lon);
}

function handleMapMouseOut({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOut', lat, lon);
}

function selectMouseCursor(state) {
  switch (state.main.tool) {
    case 'measure-dist':
    case 'measure-ele':
    case 'measure-area':
    case 'select-home-location':
    case 'map-details':
    case 'route-planner':
    case 'info-point':
      return state.routePlanner.pickMode ? 'crosshair' : 'auto';
    default:
      return isShowGalleryPicker(state) ? 'crosshair' : 'auto';
  }
}

function isShowGalleryPicker(state) {
  return (state.main.tool === null || ['gallery', 'track-viewer', 'search'].includes(state.main.tool))
    && state.map.overlays.includes('I')
    && state.gallery.pickingPositionForId === null;
}
<commit_msg>moved "locate me" button to bottom toolbar<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { Map, ScaleControl } from 'react-leaflet';
import { connect } from 'react-redux';

import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import Button from 'react-bootstrap/lib/Button';
import Panel from 'react-bootstrap/lib/Panel';

import Layers from 'fm3/components/Layers';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import Toasts from 'fm3/components/Toasts';

import SearchMenu from 'fm3/components/SearchMenu';
import SearchResults from 'fm3/components/SearchResults';

import ObjectsMenu from 'fm3/components/ObjectsMenu';
import ObjectsResult from 'fm3/components/ObjectsResult';

import MeasurementMenu from 'fm3/components/MeasurementMenu';
import DistanceMeasurementResult from 'fm3/components/DistanceMeasurementResult';
import AreaMeasurementResult from 'fm3/components/AreaMeasurementResult';
import ElevationMeasurementResult from 'fm3/components/ElevationMeasurementResult';
import LocationResult from 'fm3/components/LocationResult';

import RoutePlannerMenu from 'fm3/components/RoutePlannerMenu';
import RoutePlannerResult from 'fm3/components/RoutePlannerResult';

import TrackViewerMenu from 'fm3/components/TrackViewerMenu';
import TrackViewerResult from 'fm3/components/TrackViewerResult';

import GalleryMenu from 'fm3/components/GalleryMenu';
import GalleryResult from 'fm3/components/GalleryResult';
import GalleryPicker from 'fm3/components/GalleryPicker';

import Settings from 'fm3/components/Settings';

import OpenInExternalAppMenuButton from 'fm3/components/OpenInExternalAppMenuButton';
import ToolsMenuButton from 'fm3/components/ToolsMenuButton';
import MapSwitchButton from 'fm3/components/MapSwitchButton';
import MoreMenuButton from 'fm3/components/MoreMenuButton';

import AsyncElevationChart from 'fm3/components/AsyncElevationChart';

import InfoPointMenu from 'fm3/components/InfoPointMenu';
import InfoPoint from 'fm3/components/InfoPoint';

import ChangesetsMenu from 'fm3/components/ChangesetsMenu';
import Changesets from 'fm3/components/Changesets';

import MapDetailsMenu from 'fm3/components/MapDetailsMenu';
import MapDetails from 'fm3/components/MapDetails';

import ShareMapModal from 'fm3/components/ShareMapModal';
import EmbedMapModal from 'fm3/components/EmbedMapModal';
import LoginModal from 'fm3/components/LoginModal';

import * as FmPropTypes from 'fm3/propTypes';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';

import { mapRefocus, mapReset } from 'fm3/actions/mapActions';
import { setTool, setLocation, exportGpx, clearMap, toggleLocate } from 'fm3/actions/mainActions';
import { authCheckLogin } from 'fm3/actions/authActions';

import { setMapLeafletElement } from 'fm3/leafletElementHolder';

import 'fm3/styles/main.scss';
import 'leaflet/dist/leaflet.css';

class Main extends React.Component {
  static propTypes = {
    lat: PropTypes.number.isRequired,
    lon: PropTypes.number.isRequired,
    zoom: PropTypes.number.isRequired,
    tool: FmPropTypes.tool,
    mapType: FmPropTypes.mapType.isRequired,
    onToolSet: PropTypes.func.isRequired,
    onMapRefocus: PropTypes.func.isRequired,
    activeModal: PropTypes.string,
    progress: PropTypes.bool.isRequired,
    onLocationSet: PropTypes.func.isRequired,
    mouseCursor: PropTypes.string.isRequired,
    embeddedMode: PropTypes.bool.isRequired,
    onCheckLogin: PropTypes.func.isRequired,
    ignoreEscape: PropTypes.bool.isRequired,
    showElevationChart: PropTypes.bool.isRequired,
    showGalleryPicker: PropTypes.bool.isRequired,
    onGpxExport: PropTypes.func.isRequired,
    onMapClear: PropTypes.func.isRequired,
    onLocate: PropTypes.func.isRequired,
    locate: PropTypes.bool.isRequired,
    showLoginModal: PropTypes.bool,
    onMapReset: PropTypes.func.isRequired,
  };

  componentWillMount() {
    this.props.onCheckLogin();
  }

  componentDidMount() {
    setMapLeafletElement(this.map.leafletElement);
    document.addEventListener('keydown', (event) => {
      if (event.keyCode === 27 /* escape key */ && !this.props.ignoreEscape) {
        this.props.onToolSet(null);
      }
    });

    if (this.props.embeddedMode) {
      document.body.classList.add('embedded');
    }

    document.addEventListener('fullscreenchange', this.handleFullscreenChange);
  }

  componentWillUnmount() {
    document.removeEventListener('fullscreenchange', this.handleFullscreenChange);
    setMapLeafletElement(null);
  }

  handleFullscreenChange = () => {
    this.forceUpdate();
  }

  handleMapMoveEnd = () => {
    // TODO analyze why this can be null
    if (!this.map) {
      return;
    }

    const map = this.map.leafletElement;
    const { lat, lng: lon } = map.getCenter();
    const zoom = map.getZoom();

    if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== zoom) {
      this.props.onMapRefocus({ lat, lon, zoom });
    }
  }

  handleLocationFound = (e) => {
    this.props.onLocationSet(e.latitude, e.longitude, e.accuracy);
  }

  openFreemapInNonEmbedMode = () => {
    const currentURL = window.location.href;
    window.open(currentURL.replace('&embed=true', ''), '_blank');
  }

  handleToolCloseClick = () => {
    this.props.onToolSet(null);
  }

  handleFullscreenClick = () => {
    if (document.fullscreenElement) {
      document.exitFullscreen();
    } else {
      document.body.requestFullscreen();
    }
  }

  handleZoomInClick = () => {
    const zoom = this.map.leafletElement.getZoom() + 1;
    this.props.onMapRefocus({ zoom });
  }

  handleZoomOutClick = () => {
    const zoom = this.map.leafletElement.getZoom() - 1;
    this.props.onMapRefocus({ zoom });
  }

  render() {
    const { lat, lon, zoom, mapType,
      tool, activeModal, progress, mouseCursor, embeddedMode, showElevationChart, showGalleryPicker, onMapClear,
      showLoginModal, onMapReset } = this.props;

    const showDefaultMenu = [null, 'location'].includes(tool);

    return (

        {/* embeddedMode && <button id="freemap-logo" className="embedded" onClick={this.openFreemapInNonEmbedMode} /> */}
        <Toasts />

          <Panel className="fm-toolbar">
            <ButtonToolbar>
              <ButtonGroup>
                <Button id="freemap-logo" className={progress ? 'in-progress' : 'idle'} onClick={onMapReset} />
                <ToolsMenuButton />
                <Button onClick={onMapClear} title="Vyčistiť mapu">
                  <FontAwesomeIcon icon="eraser" />
                </Button>
                <Button onClick={this.handleFullscreenClick} title={document.fullscreenElement ? 'Zrušiť zobrazenie na celú obrazovku' : 'Na celú obrazovku'}>
                  <FontAwesomeIcon icon={document.fullscreenElement ? 'compress' : 'expand'} />
                </Button>
                <Button onClick={this.props.onGpxExport} title="Exportovať do GPX">
                  <FontAwesomeIcon icon="share" />
                </Button>
                <OpenInExternalAppMenuButton lat={lat} lon={lon} zoom={zoom} mapType={mapType} />
                <MoreMenuButton />
              </ButtonGroup>
            </ButtonToolbar>
          </Panel>
          {tool &&
            <Panel className="fm-toolbar">
              {tool === 'search' && <SearchMenu />}
              {tool === 'objects' && <ObjectsMenu />}
              {tool === 'route-planner' && <RoutePlannerMenu />}
              {['measure-dist', 'measure-ele', 'measure-area'].includes(tool) && <MeasurementMenu />}
              {tool === 'track-viewer' && <TrackViewerMenu />}
              {tool === 'info-point' && <InfoPointMenu />}
              {tool === 'changesets' && <ChangesetsMenu />}
              {tool === 'gallery' && <GalleryMenu />}
              {tool === 'map-details' && <MapDetailsMenu />}

                {' '}
                <Button onClick={this.handleToolCloseClick} title="Zavrieť nástroj" disabled={!tool}>
                  <FontAwesomeIcon icon="close" /> Zavrieť
                </Button>

            </Panel>
          }

          <Panel className="fm-toolbar">
            <ButtonToolbar>
              <MapSwitchButton />
              <ButtonGroup>
                <Button
                  onClick={this.handleZoomInClick}
                  title="Priblížiť mapu"
                  disabled={this.map && this.props.zoom >= this.map.leafletElement.getMaxZoom()}
                >
                  <FontAwesomeIcon icon="plus" />
                </Button>
                <Button
                  onClick={this.handleZoomOutClick}
                  title="Oddialiť mapu"
                  disabled={this.map && this.props.zoom <= this.map.leafletElement.getMinZoom()}
                >
                  <FontAwesomeIcon icon="minus" />
                </Button>
              </ButtonGroup>
              <Button onClick={this.props.onLocate} title="Kde som?" active={this.props.locate}>
                <FontAwesomeIcon icon="dot-circle-o" />
              </Button>
            </ButtonToolbar>
          </Panel>

        {activeModal === 'settings' && <Settings />}
        {activeModal === 'share' && <ShareMapModal />}
        {activeModal === 'embed' && <EmbedMapModal />}
        {showLoginModal && <LoginModal />}
        {/* TODO !embeddedMode && <ProgressIndicator active={progress} /> */}

        <div className={`map-holder active-map-type-${mapType}`}>
          <Map
            zoomControl={false}
            minZoom={8}
            ref={(map) => { this.map = map; }}
            center={L.latLng(lat, lon)}
            zoom={zoom}
            onMoveend={this.handleMapMoveEnd}
            onMousemove={handleMapMouseMove}
            onMouseover={handleMapMouseOver}
            onMouseout={handleMapMouseOut}
            onClick={handleMapClick}
            onLocationfound={this.handleLocationFound}
            style={{ cursor: mouseCursor }}
            maxBounds={[[47.040256, 15.4688], [49.837969, 23.906238]]}
          >
            <ScaleControl imperial={false} position="bottomleft" />
            <Layers />

            {(showDefaultMenu || tool === 'search') && <SearchResults />}

            <ObjectsResult />
            <RoutePlannerResult />
            <DistanceMeasurementResult />
            <ElevationMeasurementResult />
            <AreaMeasurementResult />
            <LocationResult />
            <TrackViewerResult />
            <InfoPoint />

            <Changesets />
            {tool === 'map-details' && <MapDetails />}
            {showElevationChart && <AsyncElevationChart />}
            {showGalleryPicker && <GalleryPicker />}
            <GalleryResult />
          </Map>

    );
  }
}

export default connect(
  state => ({
    lat: state.map.lat,
    lon: state.map.lon,
    zoom: state.map.zoom,
    tool: state.main.tool,
    mapType: state.map.mapType,
    activeModal: state.main.activeModal,
    progress: !!state.main.progress.length,
    mouseCursor: selectMouseCursor(state),
    embeddedMode: state.main.embeddedMode,
    user: state.auth.user,
    ignoreEscape: !!(state.main.activeModal && state.main.activeModal !== 'settings' // TODO settings dialog gets also closed
      || state.gallery.activeImageId),
    showElevationChart: !!state.elevationChart.elevationProfilePoints,
    showGalleryPicker: isShowGalleryPicker(state),
    locate: state.main.locate,
    showLoginModal: state.auth.chooseLoginMethod,
  }),
  dispatch => ({
    onToolSet(tool) {
      dispatch(setTool(tool));
    },
    onMapRefocus(changes) {
      dispatch(mapRefocus(changes));
    },
    onLocationSet(lat, lon, accuracy) {
      dispatch(setLocation(lat, lon, accuracy));
    },
    onCheckLogin() {
      dispatch(authCheckLogin());
    },
    onGpxExport() {
      dispatch(exportGpx());
    },
    onMapClear() {
      dispatch(clearMap());
    },
    onMapReset() {
      dispatch(mapReset());
    },
    onLocate() {
      dispatch(toggleLocate());
    },
  }),
)(Main);

function handleMapClick({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mapClick', lat, lon);
}

function handleMapMouseMove({ latlng: { lat, lng: lon }, originalEvent }) {
  mapEventEmitter.emit('mouseMove', lat, lon, originalEvent);
}

function handleMapMouseOver({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOver', lat, lon);
}

function handleMapMouseOut({ latlng: { lat, lng: lon } }) {
  mapEventEmitter.emit('mouseOut', lat, lon);
}

function selectMouseCursor(state) {
  switch (state.main.tool) {
    case 'measure-dist':
    case 'measure-ele':
    case 'measure-area':
    case 'select-home-location':
    case 'map-details':
    case 'route-planner':
    case 'info-point':
      return state.routePlanner.pickMode ? 'crosshair' : 'auto';
    default:
      return isShowGalleryPicker(state) ? 'crosshair' : 'auto';
  }
}

function isShowGalleryPicker(state) {
  return (state.main.tool === null || ['gallery', 'track-viewer', 'search'].includes(state.main.tool))
    && state.map.overlays.includes('I')
    && state.gallery.pickingPositionForId === null;
}
<|endoftext|>


<commit_before>import React, { Fragment } from 'react';
import PropTypes from 'prop-types';

import { withUser } from './UserProvider';

import Body from '../components/Body';
import ErrorPage from '../components/ErrorPage';
import Footer from '../components/Footer';
import Header from '../components/Header';

class Page extends React.Component {
  render() {
    const { children, data = {}, loadingLoggedInUser, LoggedInUser, title, showSearch } = this.props;

    if (data.error) {
      return <ErrorPage data={data} LoggedInUser={LoggedInUser} />;
    }

    const childProps = { LoggedInUser, loadingLoggedInUser };

    return (
      <Fragment>

        {typeof children === 'function' ? children(childProps) : children}

      </Fragment>
    );
  }
}

Page.propTypes = {
  data: PropTypes.shape({
    error: PropTypes.shape({}),
  }),
  LoggedInUser: PropTypes.shape({}),
  showSearch: PropTypes.bool,
  title: PropTypes.string,
};

Page.defaultProps = {
  showSearch: true,
};

export default withUser(Page);
<commit_msg>feat(components/Page): simplify into functional component<commit_after>import React, { Fragment } from 'react';
import PropTypes from 'prop-types';

import { withUser } from './UserProvider';

import Body from '../components/Body';
import ErrorPage from '../components/ErrorPage';
import Footer from '../components/Footer';
import Header from '../components/Header';

const Page = ({ children, data = {}, loadingLoggedInUser, LoggedInUser, title, showSearch }) => {
  if (data.error) {
    return <ErrorPage data={data} LoggedInUser={LoggedInUser} />;
  }

  const childProps = { LoggedInUser, loadingLoggedInUser };

  return (
    <Fragment>

      {typeof children === 'function' ? children(childProps) : children}

    </Fragment>
  );
};

Page.displayName = 'Page';

Page.propTypes = {
  data: PropTypes.shape({
    error: PropTypes.shape({}),
  }),
  loadingLoggedInUser: PropTypes.bool,
  LoggedInUser: PropTypes.shape({}),
  showSearch: PropTypes.bool,
  title: PropTypes.string,
};

Page.defaultProps = {
  showSearch: true,
};

export default withUser(Page);
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import colors from '../constants/colors';
import TicketController from './TicketController';
import CTAButton from './Button';
import Currency from './Currency';
import { defineMessages, FormattedNumber, FormattedMessage } from 'react-intl';
import withIntl from '../lib/withIntl';
import { ButtonGroup, Button } from 'react-bootstrap';
import InputField from './InputField';
import { getCurrencySymbol, capitalize } from '../lib/utils';
import { get } from 'lodash';

class Tier extends React.Component {

  static propTypes = {
    tier: PropTypes.object.isRequired,
    className: PropTypes.string,
    values: PropTypes.object, // overriding values {quantity, amount, interval}
    onChange: PropTypes.func, // onChange({ id, quantity, amount, interval })
    onClick: PropTypes.func // onClick({ id, quantity, amount, interval })
  }

  constructor(props) {
    super(props);

    this.onChange = this.props.onChange || function() {};
    this.handleChange = this.handleChange.bind(this);

    this.currencyStyle = { style: 'currency', currencyDisplay: 'symbol', minimumFractionDigits: 0, maximumFractionDigits: 2};

    this.messages = defineMessages({
      'amount.label': { id: 'tier.amount.label', defaultMessage: 'amount' },
      'interval.label': { id: 'tier.interval.label', defaultMessage: 'interval' },
      'interval.onetime': { id: 'tier.interval.onetime', defaultMessage: 'one time' },
      'interval.month': { id: 'tier.interval.month', defaultMessage: 'month' },
      'interval.year': { id: 'tier.interval.year', defaultMessage: 'year' }
    });

  }

  componentDidMount() {
    const currentValues = this.calcCurrentValues();

    // handle the initial condition of starting without an amount
    if (currentValues.amount && !this.props.values.amount) {
      this.handleChange('amount', currentValues.amount);
    }
  }

  // since this is a pure component, we don't want to store state
  // But, we still need a way to construct the current values of
  // quantity, amount and interval
  calcCurrentValues() {

    const { values, tier } = this.props;

    let quantity, amount, interval, presets;

    // Case 1: handle presets. Both interval and amount are changeable
    if (tier.presets) {
      presets = tier.presets.filter(p => !isNaN(p)).map(p => parseInt(p, 10));
      interval = (values && values.interval) || null;
      amount = (values && values.amount) || presets[Math.floor(presets.length / 2)];
      quantity = 1
    } else if (tier.type === 'TICKET') {
      // Case 2: handle quantity. Can't be active at same time as presets
      quantity = (values && values.quantity) || 1;
      amount = tier.amount * quantity;
    } else if (tier.amount || values.amount) {
      // Case 3: nothing is changeable, comes with amount (and interval optional)
      interval = tier.interval || values.interval;
      amount = tier.amount || values.amount;
      quantity = 1;
    }
    return { interval, amount, quantity, presets }
  }

  handleTicketsChange(quantity) {
    const currentValues = this.calcCurrentValues();

    const response = {
      ...this.props.tier,
      quantity
    };
    if (currentValues.interval) {
      response.interval = currentValues.interval;
    }
    this.onChange(response);
  }

  handleChange(field, value) {
    const { tier } = this.props;

    const currentValues = this.calcCurrentValues();

    const response = Object.assign({}, tier, {
      amount: currentValues.amount,
      interval: currentValues.interval,
      quantity: currentValues.quantity
    });

    // Make sure that the custom amount entered by the user is never under the minimum preset amount
    if (field === 'amount' && tier.presets && tier.presets[0] >= value) {
      value = tier.presets[0];
    }

    response[field] = value;
    this.onChange(response);
  }

  render() {
    const { intl, values, tier } = this.props;
    const { type, name, description, currency } = this.props.tier;

    const intervals = [ null, 'month', 'year'];
    const currentValues = this.calcCurrentValues();
    const { quantity, amount, interval, presets } = currentValues;

    const anchor = (get(tier, 'name') || "").toLowerCase().replace(/ /g,'-');

    return (
      <div className={`${this.props.className} tier ${this.props.onClick ? 'withCTA' : ''}`} id={anchor}>

{capitalize(name)}

            { !presets &&

                { !amount && !presets && <FormattedMessage id="amount.free" defaultMessage="free" /> }
                { amount > 0 && <Currency value={amount} currency={currency} /> }
                { interval && '/' }
                {interval && interval === 'month' && intl.formatMessage(this.messages[`interval.month`])}
                {interval && interval === 'year' && intl.formatMessage(this.messages[`interval.year`])}

            }

            {description}
            { presets &&

                  <FormattedMessage id="tier.amount.select" defaultMessage="Select amount" />

                    <ButtonGroup className="presetBtnGroup">
                      { presets.map(preset => !isNaN(preset) && (
                        <Button className="presetBtn" bsStyle={amount === preset ? 'primary' : 'default'} onClick={() => this.handleChange('amount', preset)}>
                          <FormattedNumber
                            value={preset / 100}
                            currency={currency}
                            {...this.currencyStyle}
                            />
                        </Button>
                      ))}
                    </ButtonGroup>
                    <InputField
                      name='amount'
                      className="inputAmount"
                      min={tier.presets && tier.presets[0]}
                      pre={getCurrencySymbol(currency)}
                      type='currency'
                      value={amount}
                      onChange={(amount) => this.handleChange('amount', amount)} />

                  <FormattedMessage id="tier.interval.select" defaultMessage="Select frequency" />
                  <ButtonGroup className="intervalBtnGroup">
                    { intervals.map(i => (
                      <Button className="intervalBtn" bsStyle={interval === i ? 'primary' : 'default'} onClick={() => this.handleChange('interval', i)}>
                        {intl.formatMessage(this.messages[`interval.${i || 'onetime'}`])}
                      </Button>
                    ))}
                  </ButtonGroup>

            }

          { type === 'TICKET' &&

              <TicketController value={quantity} onChange={(value) => this.handleTicketsChange(value)} />
              {this.props.onClick && <CTAButton className="ctabtn blue" label={(<FormattedMessage id='tier.GetTicket' values={{ quantity }} defaultMessage={`{quantity, plural, one {get ticket} other {get tickets}}`} />)} onClick={() => this.props.onClick({id: tier.id, amount, quantity, interval})} />}

          }
          { type !== 'TICKET' && this.props.onClick &&

              <CTAButton className="ctabtn blue" label={tier.button || (<FormattedMessage id='tier.GetTier' values={{name}} defaultMessage={`become a {name}`} />)} onClick={() => this.props.onClick({id: tier.id, amount, quantity, interval})} />

          }

    );
  }
}

export default withIntl(Tier);<commit_msg>last test fix<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import colors from '../constants/colors';
import TicketController from './TicketController';
import CTAButton from './Button';
import Currency from './Currency';
import { defineMessages, FormattedNumber, FormattedMessage } from 'react-intl';
import withIntl from '../lib/withIntl';
import { ButtonGroup, Button } from 'react-bootstrap';
import InputField from './InputField';
import { getCurrencySymbol, capitalize } from '../lib/utils';
import { get } from 'lodash';

class Tier extends React.Component {

  static propTypes = {
    tier: PropTypes.object.isRequired,
    className: PropTypes.string,
    values: PropTypes.object, // overriding values {quantity, amount, interval}
    onChange: PropTypes.func, // onChange({ id, quantity, amount, interval })
    onClick: PropTypes.func // onClick({ id, quantity, amount, interval })
  }

  constructor(props) {
    super(props);

    this.onChange = this.props.onChange || function() {};
    this.handleChange = this.handleChange.bind(this);

    this.currencyStyle = { style: 'currency', currencyDisplay: 'symbol', minimumFractionDigits: 0, maximumFractionDigits: 2};

    this.messages = defineMessages({
      'amount.label': { id: 'tier.amount.label', defaultMessage: 'amount' },
      'interval.label': { id: 'tier.interval.label', defaultMessage: 'interval' },
      'interval.onetime': { id: 'tier.interval.onetime', defaultMessage: 'one time' },
      'interval.month': { id: 'tier.interval.month', defaultMessage: 'month' },
      'interval.year': { id: 'tier.interval.year', defaultMessage: 'year' }
    });

  }

  componentDidMount() {
    const currentValues = this.calcCurrentValues();
    const values = this.props.values;

    // handle the initial condition of starting without an amount
    if (currentValues.amount && (values && !values.amount)) {
      this.handleChange('amount', currentValues.amount);
    }
  }

  // since this is a pure component, we don't want to store state
  // But, we still need a way to construct the current values of
  // quantity, amount and interval
  calcCurrentValues() {

    const { values, tier } = this.props;

    let quantity, amount, interval, presets;

    // Case 1: handle presets. Both interval and amount are changeable
    if (tier.presets) {
      presets = tier.presets.filter(p => !isNaN(p)).map(p => parseInt(p, 10));
      interval = (values && values.interval) || null;
      amount = (values && values.amount) || presets[Math.floor(presets.length / 2)];
      quantity = 1
    } else if (tier.type === 'TICKET') {
      // Case 2: handle quantity. Can't be active at same time as presets
      quantity = (values && values.quantity) || 1;
      amount = tier.amount * quantity;
    } else if (tier.amount || values.amount) {
      // Case 3: nothing is changeable, comes with amount (and interval optional)
      interval = tier.interval || values.interval;
      amount = tier.amount || values.amount;
      quantity = 1;
    }
    return { interval, amount, quantity, presets }
  }

  handleTicketsChange(quantity) {
    const currentValues = this.calcCurrentValues();

    const response = {
      ...this.props.tier,
      quantity
    };
    if (currentValues.interval) {
      response.interval = currentValues.interval;
    }
    this.onChange(response);
  }

  handleChange(field, value) {
    const { tier } = this.props;

    const currentValues = this.calcCurrentValues();

    const response = Object.assign({}, tier, {
      amount: currentValues.amount,
      interval: currentValues.interval,
      quantity: currentValues.quantity
    });

    // Make sure that the custom amount entered by the user is never under the minimum preset amount
    if (field === 'amount' && tier.presets && tier.presets[0] >= value) {
      value = tier.presets[0];
    }

    response[field] = value;
    this.onChange(response);
  }

  render() {
    const { intl, values, tier } = this.props;
    const { type, name, description, currency } = this.props.tier;

    const intervals = [ null, 'month', 'year'];
    const currentValues = this.calcCurrentValues();
    const { quantity, amount, interval, presets } = currentValues;

    const anchor = (get(tier, 'name') || "").toLowerCase().replace(/ /g,'-');

    return (
      <div className={`${this.props.className} tier ${this.props.onClick ? 'withCTA' : ''}`} id={anchor}>

{capitalize(name)}

            { !presets &&

                { !amount && !presets && <FormattedMessage id="amount.free" defaultMessage="free" /> }
                { amount > 0 && <Currency value={amount} currency={currency} /> }
                { interval && '/' }
                {interval && interval === 'month' && intl.formatMessage(this.messages[`interval.month`])}
                {interval && interval === 'year' && intl.formatMessage(this.messages[`interval.year`])}

            }

            {description}
            { presets &&

                  <FormattedMessage id="tier.amount.select" defaultMessage="Select amount" />

                    <ButtonGroup className="presetBtnGroup">
                      { presets.map(preset => !isNaN(preset) && (
                        <Button className="presetBtn" bsStyle={amount === preset ? 'primary' : 'default'} onClick={() => this.handleChange('amount', preset)}>
                          <FormattedNumber
                            value={preset / 100}
                            currency={currency}
                            {...this.currencyStyle}
                            />
                        </Button>
                      ))}
                    </ButtonGroup>
                    <InputField
                      name='amount'
                      className="inputAmount"
                      min={tier.presets && tier.presets[0]}
                      pre={getCurrencySymbol(currency)}
                      type='currency'
                      value={amount}
                      onChange={(amount) => this.handleChange('amount', amount)} />

                  <FormattedMessage id="tier.interval.select" defaultMessage="Select frequency" />
                  <ButtonGroup className="intervalBtnGroup">
                    { intervals.map(i => (
                      <Button className="intervalBtn" bsStyle={interval === i ? 'primary' : 'default'} onClick={() => this.handleChange('interval', i)}>
                        {intl.formatMessage(this.messages[`interval.${i || 'onetime'}`])}
                      </Button>
                    ))}
                  </ButtonGroup>

            }

          { type === 'TICKET' &&

              <TicketController value={quantity} onChange={(value) => this.handleTicketsChange(value)} />
              {this.props.onClick && <CTAButton className="ctabtn blue" label={(<FormattedMessage id='tier.GetTicket' values={{ quantity }} defaultMessage={`{quantity, plural, one {get ticket} other {get tickets}}`} />)} onClick={() => this.props.onClick({id: tier.id, amount, quantity, interval})} />}

          }
          { type !== 'TICKET' && this.props.onClick &&

              <CTAButton className="ctabtn blue" label={tier.button || (<FormattedMessage id='tier.GetTier' values={{name}} defaultMessage={`become a {name}`} />)} onClick={() => this.props.onClick({id: tier.id, amount, quantity, interval})} />

          }

    );
  }
}

export default withIntl(Tier);<|endoftext|>


<commit_before>import React                    from 'react';
import {RouteHandler, Link} from 'react-router';
import 'styles/bootstrap.css';
import 'styles/clean-blog.css';
import CleanBlogJS from 'lib/clean-blog';
import glucoseActions from '../actions/glucoseAction';
import glucoseStore from '../stores/glucoseStore';
import trend from './trend';
import _ from 'lodash';
import moment from 'moment-timezone';
import GlucoseChart from './glucoseChart';
import NextReading from './timer';

class Main extends React.Component {
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.state = {};
    }

    componentDidMount() {
        glucoseStore.listen(this.onChange);
        var gState = glucoseStore.getState();
        var history = gState.glucose;
        if (history) {
            if (history.length > 0) {
                history = _.sortBy(history, 'time').reverse();
                gState.glucose = history[0].glucose;
                gState.history = history;
            }
        }

        this.setState(gState);
    }

    componentWillUnmount() {
        clearInterval(window.timer);
        glucoseStore.unlisten(this.onChange);
    }

    onChange(event) {
        var newState = glucoseStore.getState();
        this.setState(newState);
    }

    render() {
        var glucose = 'Not available';
        var sorted;
        if (this.state.glucose) {
            if (this.state.glucose == 401) {
                glucose = 'HIGH';
            } else {
                glucose = this.state.glucose;
            }
        }
        var currentTrend = 'Not available';
        if (this.state.trend) {
            currentTrend = trend.trendsToText(this.state.trend);
        }
        var lastReading = 'Not Available';
        if (this.state.lastEntry) {
            var last = new Date(this.state.lastEntry);
            var ampm = 'AM';
            if (last.getHours() == 0) {
                lastReading = '12:';
            } else if (last.getHours() == 12) {
                lastReading = '12:';
                ampm = 'PM'
            } else if (last.getHours() > 12) {
                lastReading = last.getHours() - 12 + ':';
                ampm = 'PM';
            } else {
                lastReading = last.getHours() + ':';
            }
            if (last.getMinutes() < 10) {
                lastReading += '0' + last.getMinutes();
            } else {
                lastReading += last.getMinutes();
            }
            if (last.getSeconds() < 10) {
                lastReading += ':0' + last.getSeconds();
            } else {
                lastReading += ':' + last.getSeconds();
            }
            lastReading += ' ' + ampm;
        }

        var noData =

;
        if (this.state.data != true) {
            noData =
Not getting any data from Dexcom
;
        }
		var displayReading = '
Current Reading: ' + glucose + ' ' + currentTrend + '
';

        return (

                    {noData}

Last Reading: {lastReading}
                                <NextReading status={this.state.status} lastEntry={this.state.lastEntry}/>

                    <GlucoseChart history={this.state.history}/>

        );
    }
}

export default Main;
<commit_msg>centering text<commit_after>import React                    from 'react';
import {RouteHandler, Link} from 'react-router';
import 'styles/bootstrap.css';
import 'styles/clean-blog.css';
import CleanBlogJS from 'lib/clean-blog';
import glucoseActions from '../actions/glucoseAction';
import glucoseStore from '../stores/glucoseStore';
import trend from './trend';
import _ from 'lodash';
import moment from 'moment-timezone';
import GlucoseChart from './glucoseChart';
import NextReading from './timer';

class Main extends React.Component {
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.state = {};
    }

    componentDidMount() {
        glucoseStore.listen(this.onChange);
        var gState = glucoseStore.getState();
        var history = gState.glucose;
        if (history) {
            if (history.length > 0) {
                history = _.sortBy(history, 'time').reverse();
                gState.glucose = history[0].glucose;
                gState.history = history;
            }
        }

        this.setState(gState);
    }

    componentWillUnmount() {
        clearInterval(window.timer);
        glucoseStore.unlisten(this.onChange);
    }

    onChange(event) {
        var newState = glucoseStore.getState();
        this.setState(newState);
    }

    render() {
        var glucose = 'Not available';
        var sorted;
        if (this.state.glucose) {
            if (this.state.glucose == 401) {
                glucose = 'HIGH';
            } else {
                glucose = this.state.glucose;
            }
        }
        var currentTrend = 'Not available';
        if (this.state.trend) {
            currentTrend = trend.trendsToText(this.state.trend);
        }
        var lastReading = 'Not Available';
        if (this.state.lastEntry) {
            var last = new Date(this.state.lastEntry);
            var ampm = 'AM';
            if (last.getHours() == 0) {
                lastReading = '12:';
            } else if (last.getHours() == 12) {
                lastReading = '12:';
                ampm = 'PM'
            } else if (last.getHours() > 12) {
                lastReading = last.getHours() - 12 + ':';
                ampm = 'PM';
            } else {
                lastReading = last.getHours() + ':';
            }
            if (last.getMinutes() < 10) {
                lastReading += '0' + last.getMinutes();
            } else {
                lastReading += last.getMinutes();
            }
            if (last.getSeconds() < 10) {
                lastReading += ':0' + last.getSeconds();
            } else {
                lastReading += ':' + last.getSeconds();
            }
            lastReading += ' ' + ampm;
        }

        var noData =

;
        if (this.state.data != true) {
            noData =
Not getting any data from Dexcom
;
        }
		var displayReading = '
Current Reading: ' + glucose + ' ' + currentTrend + '
';

        return (

                    {noData}

Last Reading: {lastReading}
                                <NextReading status={this.state.status} lastEntry={this.state.lastEntry}/>

                    <GlucoseChart history={this.state.history}/>

        );
    }
}

export default Main;
<|endoftext|>