<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import cx from 'classnames';
import findNodeHandle from 'react-native-web/dist/cjs/exports/findNodeHandle';
import ModalContext from './ModalContext';
import ModalBody from './ModalBody';
import ModalFooter from './ModalFooter';
import ModalHeader from './ModalHeader';
import ModalTitle from './ModalTitle';
import { MODAL_SIZES } from '../../utils/constants';
import BaseView from '../../utils/rnw-compat/BaseView';
import useModal from './useModal';

const propTypes = {
  children: PropTypes.node.isRequired,
  visible: PropTypes.bool.isRequired,
  size: PropTypes.oneOf(MODAL_SIZES),
  backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
  scrollable: PropTypes.bool,
  centered: PropTypes.bool,
  onToggle: PropTypes.func.isRequired,
};

const Modal = React.forwardRef((props, ref) => {
  const {
    visible,
    size,
    backdrop = true,
    scrollable = false,
    centered = false,
    onToggle,
    ...elementProps
  } = props;

  const modal = useModal(visible, onToggle);

  // Return null if not mounted.
  if (!modal.mounted || !modal.visible) {
    return null;
  }

  const dialogClasses = cx(
    // constant classes
    'modal-dialog',
    // variable classes
    size === 'sm' && 'modal-sm',
    size === 'lg' && 'modal-lg',
    size === 'xl' && 'modal-xl',
    scrollable && 'modal-dialog-scrollable',
    centered && 'modal-dialog-centered',
  );

  const modalElement = (
    <BaseView
      key="modal"
      ref={(element) => {
        modal.ref.current = findNodeHandle(element);
      }}
      accessible
      accessibilityRole="dialog"
      aria-labelledby={modal.identifier}
      aria-modal="true"
      // For now we need onClick here, because onMouseDown would also toggle the modal when the user clicks on a scrollbar.
      onClick={(event) => {
        if (backdrop === 'static') {
          return;
        }

        if (event.target === modal.ref.current) {
          modal.setVisible(false);
        }
      }}
      onKeyUp={(event) => {
        if (event.key === 'Escape') {
          event.preventDefault();

          if (backdrop === 'static') {
            return;
          }

          modal.setVisible(false);
        }
      }}
      essentials={{ className: 'modal show' }}
    >
      <BaseView
        accessibilityRole="document"
        essentials={{ className: dialogClasses }}
      >
        <ModalContext.Provider value={modal}>
          <BaseView
            {...elementProps}
            ref={ref}
            essentials={{ className: 'modal-content' }}
          />
        </ModalContext.Provider>
      </BaseView>
    </BaseView>
  );

  if (!backdrop) {
    return ReactDOM.createPortal(modalElement, document.body);
  }

  const backdropElement = (
    <BaseView
      key="modal-backdrop"
      essentials={{ className: 'modal-backdrop show' }}
    />
  );

  return ReactDOM.createPortal([modalElement, backdropElement], document.body);
});

Modal.displayName = 'Modal';
Modal.propTypes = propTypes;

Modal.Context = ModalContext;
Modal.Body = ModalBody;
Modal.Footer = ModalFooter;
Modal.Header = ModalHeader;
Modal.Title = ModalTitle;

export default Modal;
<commit_msg>Fix click from inside to outside for modal backdrop<commit_after>import React, { useRef } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import cx from 'classnames';
import findNodeHandle from 'react-native-web/dist/cjs/exports/findNodeHandle';
import ModalContext from './ModalContext';
import ModalBody from './ModalBody';
import ModalFooter from './ModalFooter';
import ModalHeader from './ModalHeader';
import ModalTitle from './ModalTitle';
import { MODAL_SIZES } from '../../utils/constants';
import BaseView from '../../utils/rnw-compat/BaseView';
import useModal from './useModal';

const propTypes = {
  children: PropTypes.node.isRequired,
  visible: PropTypes.bool.isRequired,
  size: PropTypes.oneOf(MODAL_SIZES),
  backdrop: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['static'])]),
  scrollable: PropTypes.bool,
  centered: PropTypes.bool,
  onToggle: PropTypes.func.isRequired,
};

const Modal = React.forwardRef((props, ref) => {
  const {
    visible,
    size,
    backdrop = true,
    scrollable = false,
    centered = false,
    onToggle,
    ...elementProps
  } = props;

  const modal = useModal(visible, onToggle);
  const waitingForMouseUp = useRef(false);
  const ignoreBackdropClick = useRef(false);

  // Return null if not mounted.
  if (!modal.mounted || !modal.visible) {
    return null;
  }

  const dialogClasses = cx(
    // constant classes
    'modal-dialog',
    // variable classes
    size === 'sm' && 'modal-sm',
    size === 'lg' && 'modal-lg',
    size === 'xl' && 'modal-xl',
    scrollable && 'modal-dialog-scrollable',
    centered && 'modal-dialog-centered',
  );

  const modalElement = (
    <BaseView
      key="modal"
      ref={(element) => {
        modal.ref.current = findNodeHandle(element);
      }}
      accessible
      accessibilityRole="dialog"
      aria-labelledby={modal.identifier}
      aria-modal="true"
      // For now we need onClick here, because onMouseDown would also toggle the modal when the user clicks on a scrollbar.
      onClick={(event) => {
        if (backdrop === 'static') {
          return;
        }

        if (
          ignoreBackdropClick.current ||
          event.target !== event.currentTarget
        ) {
          ignoreBackdropClick.current = false;
          return;
        }

        modal.setVisible(false);
      }}
      onMouseUp={(event) => {
        if (waitingForMouseUp.current && event.target === modal.ref.current) {
          ignoreBackdropClick.current = true;
        }

        waitingForMouseUp.current = false;
      }}
      onKeyUp={(event) => {
        if (event.key !== 'Escape') {
          return;
        }

        event.preventDefault();

        if (backdrop === 'static') {
          return;
        }

        modal.setVisible(false);
      }}
      essentials={{ className: 'modal show' }}
    >
      <BaseView
        accessibilityRole="document"
        onMouseDown={() => {
          waitingForMouseUp.current = true;
        }}
        essentials={{ className: dialogClasses }}
      >
        <ModalContext.Provider value={modal}>
          <BaseView
            {...elementProps}
            ref={ref}
            essentials={{ className: 'modal-content' }}
          />
        </ModalContext.Provider>
      </BaseView>
    </BaseView>
  );

  if (!backdrop) {
    return ReactDOM.createPortal(modalElement, document.body);
  }

  const backdropElement = (
    <BaseView
      key="modal-backdrop"
      essentials={{ className: 'modal-backdrop show' }}
    />
  );

  return ReactDOM.createPortal([modalElement, backdropElement], document.body);
});

Modal.displayName = 'Modal';
Modal.propTypes = propTypes;

Modal.Context = ModalContext;
Modal.Body = ModalBody;
Modal.Footer = ModalFooter;
Modal.Header = ModalHeader;
Modal.Title = ModalTitle;

export default Modal;
<|endoftext|>


<commit_before>/**
 * Copyright 2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
	"use strict";
	var path = require('path');

	function awsNodeBroker(n) {
		RED.nodes.createNode(this, n);
		this.clientId = n.clientId;
		this.endpoint = n.endpoint;
		var self = this;

		this.connect = function(clientId, reconnect, callback) {
			clientId = clientId || n.clientId;
			clientId = clientId.trim();
			var awsCerts = n.awscerts || ".";
			if (!self.device || reconnect) {
				self.log("Attempt to connect to " + n.mode + " with " + clientId + " from: " + awsCerts);
				var keyPath = path.join(awsCerts, '/' + clientId + '.private.key');
				var certPath = path.join(awsCerts, '/' + clientId + '.cert.pem');
				var caPath = path.join(awsCerts, '/root-CA.crt');
				self.log("Using the certificates that are presented as follows:");
				self.log(" - keyPath   : " + keyPath);
				self.log(" - certPath  : " + certPath);
				self.log(" - caPath    : " + caPath);
				if (n.mode == "shadow") {
					self.device = require('aws-iot-device-sdk').thingShadow({
						keyPath : keyPath,
						certPath : certPath,
						caPath : caPath,
						clientId : clientId,
						host : self.endpoint,
						protocol: 'mqtts'
					});
				} else {
					self.device = require('aws-iot-device-sdk').device({
						keyPath : keyPath,
						certPath : certPath,
						caPath : caPath,
						clientId : clientId,
						host : self.endpoint,
						protocol: 'mqtts'
					});
				}
				if (self.device) {
					self.device.on('connect', function() {
						callback('connected');
					});
					self.device.on('reconnect', function() {
						callback('reconnected');
					});
					self.device.on('error', function(error) {
						callback('error', error);
					});
					self.device.on('offline', function() {
						callback('offline');
					});
				}
				callback('created');
			} else if (self.device) {
				callback('ready');
			}
		};

		self.on('close', function() {
			self.log("closed " + self.clientId + " ok");
			if (self.device) {
				if (n.mode == "shadow") {
					self.device.unregister(self.clientId);
					self.device.end();
				} else {
					self.device.end();
				}
			}
			self.device = null;
		});
	}

	RED.nodes.registerType("aws-iot-device", awsNodeBroker);

	function awsMqttNodeIn(n) {
		RED.nodes.createNode(this, n);
		this.myDevice = n.device;
		this.awsIot = RED.nodes.getNode(this.myDevice);
		var self = this;
		var options = {
			qos: parseInt(n.qos) || 0
		};
		if (self.awsIot) {
			self.status({
				fill : "yellow",
				shape : "ring",
				text : "subscribing..."
			});
			self.awsIot.connect(null, null, function(event, error) {
				if (event == "created") {
					self.awsIot.device.unsubscribe(n.topic);
					self.awsIot.device.subscribe(n.topic, options, function(error, granted) {
						if (!error) {
							self.status({
								fill : "green",
								shape : "ring",
								text : "subscribed"
							});
							self.awsIot.device.on('message', function(topic, payload) {
								if ( typeof payload === "string") {
								} else {
									payload = payload.toString();
								}
								console.log("RECV<", topic, payload);
								self.send({
									topic : topic,
									payload : payload
								});
							});
						} else {
							self.status({
								fill : "error",
								shape : "ring",
								text : error.message
							});
						}
					});
				}
			});
		} else {
			self.error("aws-mqtt in is not configured");
			self.status({
				fill : "red",
				shape : "ring",
				text : "not configured"
			});
		}
	}

	RED.nodes.registerType("aws-mqtt in", awsMqttNodeIn);

	function awsMqttNodeOut(n) {
		RED.nodes.createNode(this, n);
		this.myDevice = n.device;
		this.awsIot = RED.nodes.getNode(this.myDevice);
		var self = this;
		var options = {
			qos: parseInt(n.qos) || 0
		};
		self.on("input", function(msg) {
			if (self.awsIot) {
				self.awsIot.connect(msg.clientId, msg.reconnect, function(event, error) {
					if ((event == "ready" || event == "created")) {
						if (!Buffer.isBuffer(msg.payload)) {
							if ( typeof msg.payload === "object") {
								msg.payload = JSON.stringify(msg.payload);
							} else if ( typeof msg.payload !== "string") {
								msg.payload = msg.payload.toString();
							}
							msg.payload = new Buffer(msg.payload, "utf-8");
						}
						self.status({
							fill : "blue",
							shape : "ring",
							text : "sending..."
						});
						console.log("SEND>", msg.topic || n.topic, msg.payload);
						self.awsIot.device.publish(msg.topic || n.topic, msg.payload, options, function(error) {
							if (error) {
								self.status({
									fill : "red",
									shape : "ring",
									text : error.message
								});
							} else {
								self.status({
									fill : "green",
									shape : "ring",
									text : "done"
								});
							}
						});
					}
				});
			} else {
				self.error("aws-mqtt out is not configured");
				self.status({
					fill : "red",
					shape : "ring",
					text : "not configured"
				});
			}
		});
	}

	RED.nodes.registerType("aws-mqtt out", awsMqttNodeOut);

	function awsThingShadowNodeFunc(n) {
		RED.nodes.createNode(this, n);
		this.myDevice = n.device;
		this.awsIot = RED.nodes.getNode(this.myDevice);

		var self = this;
		self.on("input", function(msg) {
			if (self.awsIot) {
				self.awsIot.connect(msg.clientId, msg.reconnect, function(event, error) {
					if (event == "created") {
						self.awsIot.device.unregister(self.awsIot.clientId);
						self.awsIot.device.register(self.awsIot.clientId, {
							ignoreDeltas : true,
							persistentSubscribe : true
						});
						if (n.method == 'get') {
							self.clientToken = self.awsIot.device[n.method](self.awsIot.clientId);
						} else {
							self.clientToken = self.awsIot.device[n.method](self.awsIot.clientId, msg.payload);
						}
						self.awsIot.device.on('message', function(topic, payload) {
							self.log('onMessage: ' + topic + ", " + payload.toString());
							self.send({
								type : 'message',
								topic : topic,
								payload : JSON.parse(payload.toString())
							});
						});
						self.awsIot.device.on('delta', function(thingName, stateObject) {
							self.log('onDelta ' + thingName + ': ' + JSON.stringify(stateObject));
							self.send({
								type : 'delta',
								name : thingName,
								payload : stateObject
							});
						});
						self.awsIot.device.on('status', function(thingName, status, clientToken, stateObject) {
							if (self.clientToken == clientToken) {
								self.log('onStatus: ' + thingName + ", clientToken: " + self.clientToken);
								self.send({
									type : 'status',
									name : thingName,
									token : clientToken,
									payload : {
										status : status,
										stateObject : stateObject,
									}
								});
							}
						});
						self.awsIot.device.on('timeout', function(thingName, clientToken) {
							if (self.clientToken == clientToken) {
								self.send({
									type : 'timeout',
									name : thingName,
									token : clientToken,
									payload : {
									}
								});
							}
						});
					}
				});

			} else {
				self.error("aws-thing shadow is not configured");
			}
		});
	}

	RED.nodes.registerType("aws-thing", awsThingShadowNodeFunc);
};
<commit_msg>replaced console.log with self.log<commit_after>/**
 * Copyright 2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
	"use strict";
	var path = require('path');

	function awsNodeBroker(n) {
		RED.nodes.createNode(this, n);
		this.clientId = n.clientId;
		this.endpoint = n.endpoint;
		var self = this;

		this.connect = function(clientId, reconnect, callback) {
			clientId = clientId || n.clientId;
			clientId = clientId.trim();
			var awsCerts = n.awscerts || ".";
			if (!self.device || reconnect) {
				self.log("Attempt to connect to " + n.mode + " with " + clientId + " from: " + awsCerts);
				var keyPath = path.join(awsCerts, '/' + clientId + '.private.key');
				var certPath = path.join(awsCerts, '/' + clientId + '.cert.pem');
				var caPath = path.join(awsCerts, '/root-CA.crt');
				self.log("Using the certificates that are presented as follows:");
				self.log(" - keyPath   : " + keyPath);
				self.log(" - certPath  : " + certPath);
				self.log(" - caPath    : " + caPath);
				if (n.mode == "shadow") {
					self.device = require('aws-iot-device-sdk').thingShadow({
						keyPath : keyPath,
						certPath : certPath,
						caPath : caPath,
						clientId : clientId,
						host : self.endpoint,
						protocol: 'mqtts'
					});
				} else {
					self.device = require('aws-iot-device-sdk').device({
						keyPath : keyPath,
						certPath : certPath,
						caPath : caPath,
						clientId : clientId,
						host : self.endpoint,
						protocol: 'mqtts'
					});
				}
				if (self.device) {
					self.device.on('connect', function() {
						callback('connected');
					});
					self.device.on('reconnect', function() {
						callback('reconnected');
					});
					self.device.on('error', function(error) {
						callback('error', error);
					});
					self.device.on('offline', function() {
						callback('offline');
					});
				}
				callback('created');
			} else if (self.device) {
				callback('ready');
			}
		};

		self.on('close', function() {
			self.log("closed " + self.clientId + " ok");
			if (self.device) {
				if (n.mode == "shadow") {
					self.device.unregister(self.clientId);
					self.device.end();
				} else {
					self.device.end();
				}
			}
			self.device = null;
		});
	}

	RED.nodes.registerType("aws-iot-device", awsNodeBroker);

	function awsMqttNodeIn(n) {
		RED.nodes.createNode(this, n);
		this.myDevice = n.device;
		this.awsIot = RED.nodes.getNode(this.myDevice);
		var self = this;
		var options = {
			qos: parseInt(n.qos) || 0
		};
		if (self.awsIot) {
			self.status({
				fill : "yellow",
				shape : "ring",
				text : "subscribing..."
			});
			self.awsIot.connect(null, null, function(event, error) {
				if (event == "created") {
					self.awsIot.device.unsubscribe(n.topic);
					self.awsIot.device.subscribe(n.topic, options, function(error, granted) {
						if (!error) {
							self.status({
								fill : "green",
								shape : "ring",
								text : "subscribed"
							});
							self.awsIot.device.on('message', function(topic, payload) {
								if ( typeof payload === "string") {
								} else {
									payload = payload.toString();
								}
								self.log("RECV< " + topic + payload);
								self.send({
									topic : topic,
									payload : payload
								});
							});
						} else {
							self.status({
								fill : "error",
								shape : "ring",
								text : error.message
							});
						}
					});
				}
			});
		} else {
			self.error("aws-mqtt in is not configured");
			self.status({
				fill : "red",
				shape : "ring",
				text : "not configured"
			});
		}
	}

	RED.nodes.registerType("aws-mqtt in", awsMqttNodeIn);

	function awsMqttNodeOut(n) {
		RED.nodes.createNode(this, n);
		this.myDevice = n.device;
		this.awsIot = RED.nodes.getNode(this.myDevice);
		var self = this;
		var options = {
			qos: parseInt(n.qos) || 0
		};
		self.on("input", function(msg) {
			if (self.awsIot) {
				self.awsIot.connect(msg.clientId, msg.reconnect, function(event, error) {
					if ((event == "ready" || event == "created")) {
						if (!Buffer.isBuffer(msg.payload)) {
							if ( typeof msg.payload === "object") {
								msg.payload = JSON.stringify(msg.payload);
							} else if ( typeof msg.payload !== "string") {
								msg.payload = msg.payload.toString();
							}
							msg.payload = new Buffer(msg.payload, "utf-8");
						}
						self.status({
							fill : "blue",
							shape : "ring",
							text : "sending..."
						});
						self.log("SEND> " + msg.topic || n.topic + " " +msg.payload);
						self.awsIot.device.publish(msg.topic || n.topic, msg.payload, options, function(error) {
							if (error) {
								self.status({
									fill : "red",
									shape : "ring",
									text : error.message
								});
							} else {
								self.status({
									fill : "green",
									shape : "ring",
									text : "done"
								});
							}
						});
					}
				});
			} else {
				self.error("aws-mqtt out is not configured");
				self.status({
					fill : "red",
					shape : "ring",
					text : "not configured"
				});
			}
		});
	}

	RED.nodes.registerType("aws-mqtt out", awsMqttNodeOut);

	function awsThingShadowNodeFunc(n) {
		RED.nodes.createNode(this, n);
		this.myDevice = n.device;
		this.awsIot = RED.nodes.getNode(this.myDevice);

		var self = this;
		self.on("input", function(msg) {
			if (self.awsIot) {
				self.awsIot.connect(msg.clientId, msg.reconnect, function(event, error) {
					if (event == "created") {
						self.awsIot.device.unregister(self.awsIot.clientId);
						self.awsIot.device.register(self.awsIot.clientId, {
							ignoreDeltas : true,
							persistentSubscribe : true
						});
						if (n.method == 'get') {
							self.clientToken = self.awsIot.device[n.method](self.awsIot.clientId);
						} else {
							self.clientToken = self.awsIot.device[n.method](self.awsIot.clientId, msg.payload);
						}
						self.awsIot.device.on('message', function(topic, payload) {
							self.log('onMessage: ' + topic + ", " + payload.toString());
							self.send({
								type : 'message',
								topic : topic,
								payload : JSON.parse(payload.toString())
							});
						});
						self.awsIot.device.on('delta', function(thingName, stateObject) {
							self.log('onDelta ' + thingName + ': ' + JSON.stringify(stateObject));
							self.send({
								type : 'delta',
								name : thingName,
								payload : stateObject
							});
						});
						self.awsIot.device.on('status', function(thingName, status, clientToken, stateObject) {
							if (self.clientToken == clientToken) {
								self.log('onStatus: ' + thingName + ", clientToken: " + self.clientToken);
								self.send({
									type : 'status',
									name : thingName,
									token : clientToken,
									payload : {
										status : status,
										stateObject : stateObject,
									}
								});
							}
						});
						self.awsIot.device.on('timeout', function(thingName, clientToken) {
							if (self.clientToken == clientToken) {
								self.send({
									type : 'timeout',
									name : thingName,
									token : clientToken,
									payload : {
									}
								});
							}
						});
					}
				});

			} else {
				self.error("aws-thing shadow is not configured");
			}
		});
	}

	RED.nodes.registerType("aws-thing", awsThingShadowNodeFunc);
};
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import Translation from '../../behaviours/translation';
import {includes} from 'lodash/collection';
import {uniqueId} from 'lodash/utility';
import {snakeCase} from 'lodash/string';
import ButtonHelp from '../button-help';
import xor from 'lodash/array/xor';

const defaultProps = {
    actionsPosition: 'top'
};

const propTypes = {
    actions: PropTypes.func,
    actionsPosition: PropTypes.oneOf(['both', 'bottom', 'top']).isRequired,
    title: PropTypes.string,
    showHelp: PropTypes.bool,
    blockName: PropTypes.string
};

/**
* Panel.
*/
@Translation
class Panel extends Component {
    constructor(props) {
        super(props);
        const state = {
            spyId: uniqueId('panel_')
        };
        this.state = state;
    }

    /**
    * Render the a block container and the cild content of the block.
    * @return {DOM} React DOM element
    */
    render() {
        const {actions, actionsPosition, children, title, showHelp, blockName, ...otherProps} = this.props;
        const {spyId} = this.state;
        const shouldDisplayActionsTop = actions && includes(['both', 'top'], actionsPosition);
        const shouldDisplayActionsBottom = actions && includes(['both', 'bottom'], actionsPosition);
        return (

                    {title &&

{this.i18n(title)}

                    }
                    {shouldDisplayActionsTop &&

{actions()}

                    }
                    {showHelp && <ButtonHelp blockName={blockName || snakeCase(this.i18n(title)).split('_')[0]} />}

                    {children}

                {shouldDisplayActionsBottom &&

{actions()}

                }

        );
    }
}

//Static props.
Panel.displayName = 'Panel';
Panel.defaultProps = defaultProps;
Panel.propTypes = propTypes;

export default Panel;
<commit_msg>Displaying title header of panel, only if title, help or actions are defined<commit_after>import React, { Component, PropTypes } from 'react';
import { findDOMNode } from 'react-dom';
import Translation from '../../behaviours/translation';
import { includes } from 'lodash/collection';
import { uniqueId } from 'lodash/utility';
import { snakeCase } from 'lodash/string';
import ButtonHelp from '../button-help';
import xor from 'lodash/array/xor';

const defaultProps = {
    actionsPosition: 'top'
};

const propTypes = {
    actions: PropTypes.func,
    actionsPosition: PropTypes.oneOf(['both', 'bottom', 'top']).isRequired,
    title: PropTypes.string,
    showHelp: PropTypes.bool,
    blockName: PropTypes.string
};

/**
* Panel.
*/
@Translation
class Panel extends Component {
    constructor(props) {
        super(props);
        const state = {
            spyId: uniqueId('panel_')
        };
        this.state = state;
    }

    /**
    * Render the a block container and the cild content of the block.
    * @return {DOM} React DOM element
    */
    render() {
        const { actions, actionsPosition, children, title, showHelp, blockName, ...otherProps } = this.props;
        const { spyId } = this.state;
        const shouldDisplayActionsTop = actions && includes(['both', 'top'], actionsPosition);
        const shouldDisplayActionsBottom = actions && includes(['both', 'bottom'], actionsPosition);
        return (

                {(title || shouldDisplayActionsTop || showHelp) &&

                    {title &&

{this.i18n(title)}

                    }
                    {shouldDisplayActionsTop &&

{actions()}

                    }
                    {showHelp && <ButtonHelp blockName={blockName || snakeCase(this.i18n(title)).split('_')[0]} />}

}

                    {children}

                {shouldDisplayActionsBottom &&

{actions()}

                }

        );
    }
}

//Static props.
Panel.displayName = 'Panel';
Panel.defaultProps = defaultProps;
Panel.propTypes = propTypes;

export default Panel;
<|endoftext|>


<commit_before>// Copyright 2016, University of Colorado Boulder

/**
 * A flexible keypad that looks somewhat like a calculator or keyboard keypad.
 *
 * @author Aadish Gupta
 * @author John Blanco
 */

define( function( require ) {
  'use strict';

  // modules
  var BackspaceIcon = require( 'SCENERY_PHET/BackspaceIcon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NumberAccumulator = require( 'SCENERY_PHET/keypad/NumberAccumulator' );
  var Key = require( 'SCENERY_PHET/keypad/Key' );
  var KeyID = require( 'SCENERY_PHET/keypad/KeyID' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Tandem = require( 'TANDEM/Tandem' );

  // constants
  var DEFAULT_BUTTON_WIDTH = 35;
  var DEFAULT_BUTTON_HEIGHT = 35;
  var DEFAULT_BUTTON_FONT = new PhetFont( { size: 20 } );
  var DEFAULT_BUTTON_COLOR = 'white';
  var PLUS_CHAR = '\u002b';
  var MINUS_CHAR = '\u2212';

  /**
   * @param {Array.<Key>} layout - an array that specifies the keys and the layout, see static instance below for
   * example usage
   * @param {Object} [options]
   * @constructor
   */
  function Keypad( layout, options ) {
    Tandem.indicateUninstrumentedCode();

    options = _.extend( {
      buttonWidth: DEFAULT_BUTTON_WIDTH,
      buttonHeight: DEFAULT_BUTTON_HEIGHT,
      xSpacing: 10,
      ySpacing: 10,
      buttonColor: DEFAULT_BUTTON_COLOR,
      buttonFont: DEFAULT_BUTTON_FONT,

      // {AbstractAccumulator|null} accumulator that collects and interprets key presses, see various implementations
      // for examples
      accumulator: null
    }, options );

    Node.call( this );
    var self = this;

    // @private {function}
    this.keyAccumulator = options.accumulator ? options.accumulator : new NumberAccumulator( options );

    // @public {Property.<Array.<KeyID>>} (read-only) - a property containing an array of the keys that have been
    // accumulated
    this.accumulatedKeysProperty = this.keyAccumulator.accumulatedKeysProperty;

    // @public {Property.<string>} (read-only) - a property containing a string representation of the keys that have been
    // accumulated
    this.stringProperty = this.keyAccumulator.stringProperty;

    // @public {Property.<number>} (read-only) - a property containing a numeric representation of the keys that have
    // been accumulated
    this.valueProperty = this.keyAccumulator.valueProperty;

    // @private {Array.<RectangularPushButton>}
    this.buttonNodes = [];

    // determine number of rows and columns from the input layout
    var numRows = layout.length;
    var numColumns = 0;
    var row;
    var column;

    for ( row = 0; row < numRows; row++ ) {
      if ( layout[ row ].length > numColumns ) {
        numColumns = layout[ row ].length;
      }
    }

    // check last row to see if any button has vertical span more than 1
    var maxVerticalSpan = 1;
    for ( column = 0; column < layout[ numRows - 1 ].length; column++ ) {
      if ( layout[ numRows - 1 ][ column ] && layout[ numRows - 1 ][ column ].verticalSpan > maxVerticalSpan ) {
        maxVerticalSpan = layout[ numRows - 1 ][ column ].verticalSpan;
      }
    }
    numRows += maxVerticalSpan - 1;

    // 2D grid to check for the overlap
    var occupiedLayoutGrid = [];

    for ( row = 0; row < numRows; row++ ) {
      occupiedLayoutGrid[ row ] = [];
      for ( column = 0; column < numColumns; column++ ) {
        occupiedLayoutGrid[ row ][ column ] = 0;
      }
    }

    // interpret the layout specification
    var x;
    var y;
    for ( row = 0; row < layout.length; row++ ) {
      var startRow = row;
      for ( column = 0; column < layout[ row ].length; column++ ) {
        var button = layout[ row ][ column ];
        if ( button ) {
          var startColumn = column +
                            ( column > 0 && layout[ row ][ column - 1 ] ?
                              layout[ row ][ column - 1 ].horizontalSpan - 1 : 0 );
          var verticalSpan = button.verticalSpan;
          var horizontalSpan = button.horizontalSpan;

          // check for overlap between the buttons
          for ( x = startRow; x < ( startRow + verticalSpan ); x++ ) {
            for ( y = startColumn; y < ( startColumn + horizontalSpan ); y++ ) {
              assert && assert( !occupiedLayoutGrid[ x ][ y ], 'keys overlap in the layout' );
              occupiedLayoutGrid[ x ][ y ] = true;
            }
          }

          // create and add the buttons
          var buttonWidth = button.horizontalSpan * options.buttonWidth + ( button.horizontalSpan - 1 ) * options.xSpacing;
          var buttonHeight = button.verticalSpan * options.buttonHeight + ( button.verticalSpan - 1 ) * options.ySpacing;
          var buttonNode = createKeyNode( button, self.keyAccumulator, buttonWidth, buttonHeight, options );
          buttonNode.left = startColumn * options.buttonWidth + startColumn * options.xSpacing;
          buttonNode.top = startRow * options.buttonHeight + startRow * options.ySpacing;
          self.buttonNodes.push( buttonNode );
          self.addChild( buttonNode );
        }
      }
    }

    this.mutate( options );
  }

  sceneryPhet.register( 'Keypad', Keypad );

  /**
   * Helper function to create the display key node for the provided key object
   *
   * @param {Key} keyObject
   * @param {AbstractKeyAccumulator} keyAccumulator
   * @param {number} width
   * @param {number} height
   * @param {Object} [options]
   * @returns {RectangularPushButton} keyNode
   */
  function createKeyNode( keyObject, keyAccumulator, width, height, options ) {

    options = _.extend( {
      buttonColor: 'white',
      buttonFont: DEFAULT_BUTTON_FONT
    }, options );

    var content = ( keyObject.label instanceof Node ) ? keyObject.label :
                  new Text( keyObject.label, { font: options.buttonFont } );

    var keyNode = new RectangularPushButton( {
      content: content,
      baseColor: options.buttonColor,
      minWidth: width,
      minHeight: height,
      xMargin: 5,
      yMargin: 5,
      listener: function() {
        keyAccumulator.handleKeyPressed( keyObject.identifier );
      }
    } );
    keyNode.scale( width / keyNode.width, height / keyNode.height );
    return keyNode;
  }

  return inherit( Node, Keypad, {

    /**
     * Calls the clear function for the given accumulator
     * @public
     */
    clear: function() {
      this.keyAccumulator.clear();
    },

    /**
     * Determines whether pressing a key (except for backspace) will clear the existing value.
     * @param {boolean} clearOnNextKeyPress
     * @public
     */
    setClearOnNextKeyPress: function( clearOnNextKeyPress ) {
      this.keyAccumulator.setClearOnNextKeyPress( clearOnNextKeyPress );
    },

    /**
     * Will pressing a key (except for backspace) clear the existing value?
     * @returns {boolean}
     * @public
     */
    getClearOnNextKeyPress: function() {
      return this.keyAccumulator.getClearOnNextKeyPress();
    },

    /**
     * Cleans up references.
     * @public
     */
    dispose: function() {
      this.keyAccumulator.dispose();
      this.buttonNodes.forEach( function( buttonNode ) {
        buttonNode.dispose();
      } );
      Node.prototype.dispose.call( this );
    }
  }, {

    //------------------------------------------------------------------------------------------------------------------
    // static keypad layouts - These can be used as is for common layouts or serve as examples for creating custom
    // layouts. If the vertical span is greater than 1 is provided the column in the next row(s) has to be null.  If
    // the horizontal span is greater than 1, the next key in that row will not overlap and will be placed in the next
    // space in the grid. If a blank space is desired, null should be provided.
    //------------------------------------------------------------------------------------------------------------------

    PositiveIntegerLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( '0', KeyID.ZERO, { horizontalSpan: 2 } ), new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ) ]
    ],

    PositiveAndNegativeIntegerLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ), new Key( '0', KeyID.ZERO ), new Key( PLUS_CHAR + '/' + MINUS_CHAR, KeyID.PLUS_MINUS ) ]
    ],

    PositiveFloatingPointLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( '.', KeyID.DECIMAL ), new Key( '0', KeyID.ZERO ), new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ) ]
    ],

    PositiveAndNegativeFloatingPointLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( '0', KeyID.ZERO, { horizontalSpan: 2 } ), new Key( PLUS_CHAR + '/' + MINUS_CHAR, KeyID.PLUS_MINUS ) ],
      [ new Key( '.', KeyID.DECIMAL ), null, new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ) ]
    ],

    // Weird Layout is created for testing purposes to test the edge cases and layout capabilities
    WeirdLayout: [
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE, { horizontalSpan: 3 } ) ],
      [ null, new Key( '4', KeyID.FOUR, { horizontalSpan: 5 } ) ],
      [ new Key( '5', KeyID.FIVE, { verticalSpan: 2 } ), new Key( '6', KeyID.SIX ), new Key( '7', KeyID.SEVEN ) ],
      [ null, new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ null, new Key( '0', KeyID.ZERO, { horizontalSpan: 2, verticalSpan: 2 } ) ]
    ]

  } );
} );<commit_msg>delete redundant and incorrect "property" in comments #283<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * A flexible keypad that looks somewhat like a calculator or keyboard keypad.
 *
 * @author Aadish Gupta
 * @author John Blanco
 */

define( function( require ) {
  'use strict';

  // modules
  var BackspaceIcon = require( 'SCENERY_PHET/BackspaceIcon' );
  var inherit = require( 'PHET_CORE/inherit' );
  var NumberAccumulator = require( 'SCENERY_PHET/keypad/NumberAccumulator' );
  var Key = require( 'SCENERY_PHET/keypad/Key' );
  var KeyID = require( 'SCENERY_PHET/keypad/KeyID' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Tandem = require( 'TANDEM/Tandem' );

  // constants
  var DEFAULT_BUTTON_WIDTH = 35;
  var DEFAULT_BUTTON_HEIGHT = 35;
  var DEFAULT_BUTTON_FONT = new PhetFont( { size: 20 } );
  var DEFAULT_BUTTON_COLOR = 'white';
  var PLUS_CHAR = '\u002b';
  var MINUS_CHAR = '\u2212';

  /**
   * @param {Array.<Key>} layout - an array that specifies the keys and the layout, see static instance below for
   * example usage
   * @param {Object} [options]
   * @constructor
   */
  function Keypad( layout, options ) {
    Tandem.indicateUninstrumentedCode();

    options = _.extend( {
      buttonWidth: DEFAULT_BUTTON_WIDTH,
      buttonHeight: DEFAULT_BUTTON_HEIGHT,
      xSpacing: 10,
      ySpacing: 10,
      buttonColor: DEFAULT_BUTTON_COLOR,
      buttonFont: DEFAULT_BUTTON_FONT,

      // {AbstractAccumulator|null} accumulator that collects and interprets key presses, see various implementations
      // for examples
      accumulator: null
    }, options );

    Node.call( this );
    var self = this;

    // @private {function}
    this.keyAccumulator = options.accumulator ? options.accumulator : new NumberAccumulator( options );

    // @public {Property.<Array.<KeyID>>} (read-only) - array of the keys that have been accumulated
    this.accumulatedKeysProperty = this.keyAccumulator.accumulatedKeysProperty;

    // @public {Property.<string>} (read-only) - string representation of the keys that have been accumulated
    this.stringProperty = this.keyAccumulator.stringProperty;

    // @public {Property.<number>} (read-only) - numeric representation of the keys that have been accumulated
    this.valueProperty = this.keyAccumulator.valueProperty;

    // @private {Array.<RectangularPushButton>}
    this.buttonNodes = [];

    // determine number of rows and columns from the input layout
    var numRows = layout.length;
    var numColumns = 0;
    var row;
    var column;

    for ( row = 0; row < numRows; row++ ) {
      if ( layout[ row ].length > numColumns ) {
        numColumns = layout[ row ].length;
      }
    }

    // check last row to see if any button has vertical span more than 1
    var maxVerticalSpan = 1;
    for ( column = 0; column < layout[ numRows - 1 ].length; column++ ) {
      if ( layout[ numRows - 1 ][ column ] && layout[ numRows - 1 ][ column ].verticalSpan > maxVerticalSpan ) {
        maxVerticalSpan = layout[ numRows - 1 ][ column ].verticalSpan;
      }
    }
    numRows += maxVerticalSpan - 1;

    // 2D grid to check for the overlap
    var occupiedLayoutGrid = [];

    for ( row = 0; row < numRows; row++ ) {
      occupiedLayoutGrid[ row ] = [];
      for ( column = 0; column < numColumns; column++ ) {
        occupiedLayoutGrid[ row ][ column ] = 0;
      }
    }

    // interpret the layout specification
    var x;
    var y;
    for ( row = 0; row < layout.length; row++ ) {
      var startRow = row;
      for ( column = 0; column < layout[ row ].length; column++ ) {
        var button = layout[ row ][ column ];
        if ( button ) {
          var startColumn = column +
                            ( column > 0 && layout[ row ][ column - 1 ] ?
                              layout[ row ][ column - 1 ].horizontalSpan - 1 : 0 );
          var verticalSpan = button.verticalSpan;
          var horizontalSpan = button.horizontalSpan;

          // check for overlap between the buttons
          for ( x = startRow; x < ( startRow + verticalSpan ); x++ ) {
            for ( y = startColumn; y < ( startColumn + horizontalSpan ); y++ ) {
              assert && assert( !occupiedLayoutGrid[ x ][ y ], 'keys overlap in the layout' );
              occupiedLayoutGrid[ x ][ y ] = true;
            }
          }

          // create and add the buttons
          var buttonWidth = button.horizontalSpan * options.buttonWidth + ( button.horizontalSpan - 1 ) * options.xSpacing;
          var buttonHeight = button.verticalSpan * options.buttonHeight + ( button.verticalSpan - 1 ) * options.ySpacing;
          var buttonNode = createKeyNode( button, self.keyAccumulator, buttonWidth, buttonHeight, options );
          buttonNode.left = startColumn * options.buttonWidth + startColumn * options.xSpacing;
          buttonNode.top = startRow * options.buttonHeight + startRow * options.ySpacing;
          self.buttonNodes.push( buttonNode );
          self.addChild( buttonNode );
        }
      }
    }

    this.mutate( options );
  }

  sceneryPhet.register( 'Keypad', Keypad );

  /**
   * Helper function to create the display key node for the provided key object
   *
   * @param {Key} keyObject
   * @param {AbstractKeyAccumulator} keyAccumulator
   * @param {number} width
   * @param {number} height
   * @param {Object} [options]
   * @returns {RectangularPushButton} keyNode
   */
  function createKeyNode( keyObject, keyAccumulator, width, height, options ) {

    options = _.extend( {
      buttonColor: 'white',
      buttonFont: DEFAULT_BUTTON_FONT
    }, options );

    var content = ( keyObject.label instanceof Node ) ? keyObject.label :
                  new Text( keyObject.label, { font: options.buttonFont } );

    var keyNode = new RectangularPushButton( {
      content: content,
      baseColor: options.buttonColor,
      minWidth: width,
      minHeight: height,
      xMargin: 5,
      yMargin: 5,
      listener: function() {
        keyAccumulator.handleKeyPressed( keyObject.identifier );
      }
    } );
    keyNode.scale( width / keyNode.width, height / keyNode.height );
    return keyNode;
  }

  return inherit( Node, Keypad, {

    /**
     * Calls the clear function for the given accumulator
     * @public
     */
    clear: function() {
      this.keyAccumulator.clear();
    },

    /**
     * Determines whether pressing a key (except for backspace) will clear the existing value.
     * @param {boolean} clearOnNextKeyPress
     * @public
     */
    setClearOnNextKeyPress: function( clearOnNextKeyPress ) {
      this.keyAccumulator.setClearOnNextKeyPress( clearOnNextKeyPress );
    },

    /**
     * Will pressing a key (except for backspace) clear the existing value?
     * @returns {boolean}
     * @public
     */
    getClearOnNextKeyPress: function() {
      return this.keyAccumulator.getClearOnNextKeyPress();
    },

    /**
     * Cleans up references.
     * @public
     */
    dispose: function() {
      this.keyAccumulator.dispose();
      this.buttonNodes.forEach( function( buttonNode ) {
        buttonNode.dispose();
      } );
      Node.prototype.dispose.call( this );
    }
  }, {

    //------------------------------------------------------------------------------------------------------------------
    // static keypad layouts - These can be used as is for common layouts or serve as examples for creating custom
    // layouts. If the vertical span is greater than 1 is provided the column in the next row(s) has to be null.  If
    // the horizontal span is greater than 1, the next key in that row will not overlap and will be placed in the next
    // space in the grid. If a blank space is desired, null should be provided.
    //------------------------------------------------------------------------------------------------------------------

    PositiveIntegerLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( '0', KeyID.ZERO, { horizontalSpan: 2 } ), new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ) ]
    ],

    PositiveAndNegativeIntegerLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ), new Key( '0', KeyID.ZERO ), new Key( PLUS_CHAR + '/' + MINUS_CHAR, KeyID.PLUS_MINUS ) ]
    ],

    PositiveFloatingPointLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( '.', KeyID.DECIMAL ), new Key( '0', KeyID.ZERO ), new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ) ]
    ],

    PositiveAndNegativeFloatingPointLayout: [
      [ new Key( '7', KeyID.SEVEN ), new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ new Key( '4', KeyID.FOUR ), new Key( '5', KeyID.FIVE ), new Key( '6', KeyID.SIX ) ],
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE ) ],
      [ new Key( '0', KeyID.ZERO, { horizontalSpan: 2 } ), new Key( PLUS_CHAR + '/' + MINUS_CHAR, KeyID.PLUS_MINUS ) ],
      [ new Key( '.', KeyID.DECIMAL ), null, new Key( ( new BackspaceIcon( { scale: 1.5 } ) ), KeyID.BACKSPACE ) ]
    ],

    // Weird Layout is created for testing purposes to test the edge cases and layout capabilities
    WeirdLayout: [
      [ new Key( '1', KeyID.ONE ), new Key( '2', KeyID.TWO ), new Key( '3', KeyID.THREE, { horizontalSpan: 3 } ) ],
      [ null, new Key( '4', KeyID.FOUR, { horizontalSpan: 5 } ) ],
      [ new Key( '5', KeyID.FIVE, { verticalSpan: 2 } ), new Key( '6', KeyID.SIX ), new Key( '7', KeyID.SEVEN ) ],
      [ null, new Key( '8', KeyID.EIGHT ), new Key( '9', KeyID.NINE ) ],
      [ null, new Key( '0', KeyID.ZERO, { horizontalSpan: 2, verticalSpan: 2 } ) ]
    ]

  } );
} );<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import moment from 'moment';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';

import MatchModel from '../../models/MatchModel.js';
import TeamModel from '../../models/TeamModel.js';
import { contextTypes } from '../../utils/decorators/withContext.js';
import withViewport from '../../utils/decorators/withViewport.js';

import CardText from 'material-ui/lib/card/card-text';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import PanelGroup from 'react-bootstrap/lib/PanelGroup';
import Panel from 'react-bootstrap/lib/Panel';

import { OwnClubId } from '../../models/ClubModel.js';

import { util as storeUtil } from '../../store.js';
import * as matchActions from '../../actions/matchActions.js';

import cn from 'classnames';
import styles from './Teams.css';
import withStyles from '../../utils/decorators/withStyles.js';

export const TeamsVttl = () => <Teams competition="Vttl" />;
export const TeamsSporta = () => <Teams competition="Sporta" />;

@withViewport
@withStyles(styles)
@connect(state => {
  return {
    config: state.config,
    user: state.user,
    players: state.players,
    clubs: state.clubs,
    matches: state.matches,
    teams: state.teams,
  };
}, matchActions)
export default class Teams extends Component {
  static contextTypes = contextTypes;
  constructor(props) {
    super(props);
    this.state = {openTabKey: this._getTeamIdOfATeam(props.teams)};
  }

  static propTypes = {
    competition: PropTypes.string.isRequired,
    config: PropTypes.object.isRequired,
    user: PropTypes.object.isRequired,
    matches: ImmutablePropTypes.listOf(PropTypes.instanceOf(MatchModel).isRequired).isRequired,
    teams: ImmutablePropTypes.listOf(PropTypes.instanceOf(TeamModel).isRequired).isRequired,
    viewport: PropTypes.object.isRequired,
    getMatchesForTeam: PropTypes.func.isRequired
  }

  componentDidMount(){
    this.props.getMatchesForTeam(this._getTeamIdOfATeam(this.props.teams));
  }

  _getTeamIdOfATeam(teams) {
    return teams.find(x => x.competition === this.props.competition && x.teamCode === 'A').id;
  }

  _showAccordion() {
    // Otherwise show tabs
    return this.props.viewport.width < 700;
  }
  _onTabSelect(teamId) {
    if (this.props.config.get('matchesForTeamLoaded').indexOf(teamId) === -1) {
      this.props.getMatchesForTeam(teamId);
    }
    this.setState({openTabKey: teamId});
  }

  _getTeamsPanel() {
    var teams = this.props.teams.filter(team => team.competition === this.props.competition);
    return teams.map(team =>
      <Panel header={this._renderRanking(team)} eventKey={team.id} onClick={this._onTabSelect.bind(this, team.id)}>
          {this._getTeamMatches(team)}
          {this._getRankingOfDivision(team)}
      </Panel>
    );
  }

  _renderRanking(team){
    var style = this._getLabelClassName(team);
    return (
{team.competition + ' ' + team.teamCode}
      {this._renderOwnTeamPosition(team)}

    );
  }

  _renderOwnTeamPosition(team) {
    const ranking = team.getDivisionRanking();
    return ranking ? ranking.position : '?';
  }

  _openMatch(matchId) {
    const matchRoute = this.context.t.route('match', {matchId: matchId});
    browserHistory.push(matchRoute);
  }

  _getTeamMatches(team){
    var matchesForTeam = team.getMatches().sort((a,b) => a.date - b.date);
    return (

                {this.context.t('teamCalendar.date')}
                {this.context.t('teamCalendar.hour')}
                {this.context.t('teamCalendar.match')}
                {this.context.t('teamCalendar.score')}

             {matchesForTeam.map(match => {
               return this._getMatch(match,team);
             })}

      );
  }

  _getMatch(match,team) {
    return (

          {match.date.format('DD/MM')}
          {match.date.format('HH:mm')}
          {match.isHomeMatch ? this._renderOwnTeamTitle(team) : this._renderOpponentTitle(match)} -
             {match.isHomeMatch ? ' ' + this._renderOpponentTitle(match) : ' ' + this._renderOwnTeamTitle(team)}
          {this._renderScores(match)}

    );
  }

  _won(match) {
    if (match.score.home === match.score.out) {
      return false;
    }

    if (match.isHomeMatch) {
      return match.score.home > match.score.out;
    }
    else {
      return match.score.out > match.score.home;
    }
  }

  _getRankingOfDivision(team) {
    var teamsForRanking = team.ranking;
    if (this._showAccordion()) {
      return (

                  {this.context.t('teamCalendar.position')}
                  {this.context.t('teamCalendar.name')}
                  {this.context.t('teamCalendar.points')}

               {teamsForRanking.map(teamRanking => {
                 return this._getRankingAccordeon(teamRanking);
               })}

      );
    }
    return (

                {this.context.t('teamCalendar.position')}
                {this.context.t('teamCalendar.name')}
                {this.context.t('teamCalendar.matchesWon')}
                {this.context.t('teamCalendar.matchesLost')}
                {this.context.t('teamCalendar.matchesDraw')}
                {this.context.t('teamCalendar.points')}

             {teamsForRanking.map(teamRanking => {
               return this._getRanking(teamRanking);
             })}

    );
  }

  _getTeamName(clubId) {
    return storeUtil.getClub(clubId).name;
  }

  _getRankingAccordeon(teamRanking) {
    return (

          {teamRanking.position}
          {storeUtil.getClub(teamRanking.clubId).name + ' ' + teamRanking.teamCode}
          {teamRanking.points}

    );
  }

  _getRanking(teamRanking) {
    return (

          {teamRanking.position}
          {storeUtil.getClub(teamRanking.clubId).name + ' ' + teamRanking.teamCode}
          {teamRanking.gamesWon}
          {teamRanking.gamesLost}
          {teamRanking.gamesDraw}
          {teamRanking.points}

     );
  }

  _renderScores(match){
    if (match.score.home === 0 && match.score.out === 0) {
      return '';
    } else {
      return match.score.home + ' - ' + match.score.out;
    }
  }

  _renderOwnTeamTitle(team) {
    return team.competition + ' ' + team.teamCode;
  }

  _renderOpponentTitle(match) {
    const club = match.getOpponentClub();
    return club.name + ' ' + match.opponent.teamCode;
  }

  _getTeamsNavigation() {
    var teams = this.props.teams.filter(team => team.competition === this.props.competition);
    return teams.map(team =>
       this._getTeamsNavigationTab(team)
    );
  }

  _getTeamsNavigationTab(team) {
    var style = this._getLabelClassName(team);
    return ( <NavItem eventKey={team.id}>

{team.competition + ' ' + team.teamCode}
          {this._renderOwnTeamPosition(team)}

      </NavItem>
    );
  }

  _getLabelClassName(team) {
    if (team.isTopper(this._renderOwnTeamPosition(team))) {return 'label label-as-badge label-success';}
    if (team.isInDegradationZone(this._renderOwnTeamPosition(team))) {return 'label label-as-badge label-danger';}
    return 'label label-as-badge label-default';
  }

  _getDivsForTeamsNavigation() {
    var team = this.props.teams.find(t => t.id === this.state.openTabKey);
    return (

        {this._getTeamMatches(team)}
        {this._getRankingOfDivision(team)}

    );
  }

  render() {

    if (this._showAccordion()) {
      return (

          <CardText expandable={true} style={{paddingTop: 0, paddingLeft: 5, paddingRight: 5}}>
            <PanelGroup activeKey={this.state.openTabKey} onSelect={::this._onTabSelect} accordion>
              {this._getTeamsPanel()}
            </PanelGroup>
          </CardText>

      );
    }

    return (

        <CardText expandable={true} style={{paddingTop: 0}}>

            {this._getTeamsNavigation()}

          {this._getDivsForTeamsNavigation()}
        </CardText>

    );
  }
}<commit_msg>Added icon to show thriller matches for teams in team calendar page<commit_after>import React, { PropTypes, Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import moment from 'moment';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';

import MatchModel from '../../models/MatchModel.js';
import TeamModel from '../../models/TeamModel.js';
import { contextTypes } from '../../utils/decorators/withContext.js';
import withViewport from '../../utils/decorators/withViewport.js';

import CardText from 'material-ui/lib/card/card-text';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import PanelGroup from 'react-bootstrap/lib/PanelGroup';
import Panel from 'react-bootstrap/lib/Panel';

import { OwnClubId } from '../../models/ClubModel.js';

import { util as storeUtil } from '../../store.js';
import * as matchActions from '../../actions/matchActions.js';

import cn from 'classnames';
import styles from './Teams.css';
import withStyles from '../../utils/decorators/withStyles.js';
import Icon from '../controls/Icon.js';

export const TeamsVttl = () => <Teams competition="Vttl" />;
export const TeamsSporta = () => <Teams competition="Sporta" />;

@withViewport
@withStyles(styles)
@connect(state => {
  return {
    config: state.config,
    user: state.user,
    players: state.players,
    clubs: state.clubs,
    matches: state.matches,
    teams: state.teams,
  };
}, matchActions)
export default class Teams extends Component {
  static contextTypes = contextTypes;
  constructor(props) {
    super(props);
    this.state = {openTabKey: this._getTeamIdOfATeam(props.teams)};
  }

  static propTypes = {
    competition: PropTypes.string.isRequired,
    config: PropTypes.object.isRequired,
    user: PropTypes.object.isRequired,
    matches: ImmutablePropTypes.listOf(PropTypes.instanceOf(MatchModel).isRequired).isRequired,
    teams: ImmutablePropTypes.listOf(PropTypes.instanceOf(TeamModel).isRequired).isRequired,
    viewport: PropTypes.object.isRequired,
    getMatchesForTeam: PropTypes.func.isRequired
  }

  componentDidMount(){
    this.props.getMatchesForTeam(this._getTeamIdOfATeam(this.props.teams));
  }

  _getTeamIdOfATeam(teams) {
    return teams.find(x => x.competition === this.props.competition && x.teamCode === 'A').id;
  }

  _showAccordion() {
    // Otherwise show tabs
    return this.props.viewport.width < 700;
  }
  _onTabSelect(teamId) {
    if (this.props.config.get('matchesForTeamLoaded').indexOf(teamId) === -1) {
      this.props.getMatchesForTeam(teamId);
    }
    this.setState({openTabKey: teamId});
  }

  _getTeamsPanel() {
    var teams = this.props.teams.filter(team => team.competition === this.props.competition);
    return teams.map(team =>
      <Panel header={this._renderRanking(team)} eventKey={team.id} onClick={this._onTabSelect.bind(this, team.id)}>
          {this._getTeamMatches(team)}
          {this._getRankingOfDivision(team)}
      </Panel>
    );
  }

  _renderRanking(team){
    var style = this._getLabelClassName(team);
    return (
{team.competition + ' ' + team.teamCode}
      {this._renderOwnTeamPosition(team)}

    );
  }

  _renderOwnTeamPosition(team) {
    const ranking = team.getDivisionRanking();
    return ranking ? ranking.position : '?';
  }

  _openMatch(matchId) {
    const matchRoute = this.context.t.route('match', {matchId: matchId});
    browserHistory.push(matchRoute);
  }

  _getTeamMatches(team){
    var matchesForTeam = team.getMatches().sort((a,b) => a.date - b.date);
    return (

                {this.context.t('teamCalendar.date')}
                {this.context.t('teamCalendar.hour')}
                {this.context.t('teamCalendar.match')}
                {this.context.t('teamCalendar.score')}

             {matchesForTeam.map(match => {
               return this._getMatch(match,team);
             })}

      );
  }

  _getMatch(match,team) {
    return (

          {this._renderThrillerIcon(team,match)}{match.date.format('DD/MM')}
          {match.date.format('HH:mm')}
          {match.isHomeMatch ? this._renderOwnTeamTitle(team) : this._renderOpponentTitle(match)} -
             {match.isHomeMatch ? ' ' + this._renderOpponentTitle(match) : ' ' + this._renderOwnTeamTitle(team)}
          {this._renderScores(match)}

    );
  }

  _renderThrillerIcon(team,match){
    if(team.getThriller(match)){
      return (<Icon fa="fa fa-heartbeat faa-pulse animated" style={{marginLeft: 3, marginRight: 7, marginTop: 3}} />);
    }
  }

  _won(match) {
    if (match.score.home === match.score.out) {
      return false;
    }

    if (match.isHomeMatch) {
      return match.score.home > match.score.out;
    }
    else {
      return match.score.out > match.score.home;
    }
  }

  _getRankingOfDivision(team) {
    var teamsForRanking = team.ranking;
    if (this._showAccordion()) {
      return (

                  {this.context.t('teamCalendar.position')}
                  {this.context.t('teamCalendar.name')}
                  {this.context.t('teamCalendar.points')}

               {teamsForRanking.map(teamRanking => {
                 return this._getRankingAccordeon(teamRanking);
               })}

      );
    }
    return (

                {this.context.t('teamCalendar.position')}
                {this.context.t('teamCalendar.name')}
                {this.context.t('teamCalendar.matchesWon')}
                {this.context.t('teamCalendar.matchesLost')}
                {this.context.t('teamCalendar.matchesDraw')}
                {this.context.t('teamCalendar.points')}

             {teamsForRanking.map(teamRanking => {
               return this._getRanking(teamRanking);
             })}

    );
  }

  _getTeamName(clubId) {
    return storeUtil.getClub(clubId).name;
  }

  _getRankingAccordeon(teamRanking) {
    return (

          {teamRanking.position}
          {storeUtil.getClub(teamRanking.clubId).name + ' ' + teamRanking.teamCode}
          {teamRanking.points}

    );
  }

  _getRanking(teamRanking) {
    return (

          {teamRanking.position}
          {storeUtil.getClub(teamRanking.clubId).name + ' ' + teamRanking.teamCode}
          {teamRanking.gamesWon}
          {teamRanking.gamesLost}
          {teamRanking.gamesDraw}
          {teamRanking.points}

     );
  }

  _renderScores(match){
    if (match.score.home === 0 && match.score.out === 0) {
      return '';
    } else {
      return match.score.home + ' - ' + match.score.out;
    }
  }

  _renderOwnTeamTitle(team) {
    return team.competition + ' ' + team.teamCode;
  }

  _renderOpponentTitle(match) {
    const club = match.getOpponentClub();
    return club.name + ' ' + match.opponent.teamCode;
  }

  _getTeamsNavigation() {
    var teams = this.props.teams.filter(team => team.competition === this.props.competition);
    return teams.map(team =>
       this._getTeamsNavigationTab(team)
    );
  }

  _getTeamsNavigationTab(team) {
    var style = this._getLabelClassName(team);
    return ( <NavItem eventKey={team.id}>

{team.competition + ' ' + team.teamCode}
          {this._renderOwnTeamPosition(team)}

      </NavItem>
    );
  }

  _getLabelClassName(team) {
    if (team.isTopper(this._renderOwnTeamPosition(team))) {return 'label label-as-badge label-success';}
    if (team.isInDegradationZone(this._renderOwnTeamPosition(team))) {return 'label label-as-badge label-danger';}
    return 'label label-as-badge label-default';
  }

  _getDivsForTeamsNavigation() {
    var team = this.props.teams.find(t => t.id === this.state.openTabKey);
    return (

        {this._getTeamMatches(team)}
        {this._getRankingOfDivision(team)}

    );
  }

  render() {

    if (this._showAccordion()) {
      return (

          <CardText expandable={true} style={{paddingTop: 0, paddingLeft: 5, paddingRight: 5}}>
            <PanelGroup activeKey={this.state.openTabKey} onSelect={::this._onTabSelect} accordion>
              {this._getTeamsPanel()}
            </PanelGroup>
          </CardText>

      );
    }

    return (

        <CardText expandable={true} style={{paddingTop: 0}}>

            {this._getTeamsNavigation()}

          {this._getDivsForTeamsNavigation()}
        </CardText>

    );
  }
}<|endoftext|>


<commit_before>define("amber_vm/smalltalk", ["./boot"], function (boot) {
    return boot.smalltalk;
});
<commit_msg>smalltalk catches amd require to use in amd transport<commit_after>define("amber_vm/smalltalk", ["require", "./boot"], function (require, boot) {
    var smalltalk = boot.smalltalk;
    smalltalk._amd_require = require;
    return  smalltalk;
});
<|endoftext|>


<commit_before>L.Control.MiniMap = L.Control.extend({
	options: {
		position: 'bottomright',
		zoomLevelOffset: -5,
		zoomLevelFixed: false,
		zoomAnimation: false
	},
	//layer is the map layer to be shown in the minimap
	initialize: function (layer, options) {
		L.Util.setOptions(this, options);
		this._layer = layer;
	},

	onAdd: function (map) {

		this._mainMap = map;

		//Creating the container and stopping events from spilling through to the main map.
		this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
		L.DomEvent.disableClickPropagation(this._container);
		L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);

		this._miniMap = new L.Map(this._container,
		{
			attributionControl: false,
			zoomControl: false,
			zoomAnimation: this.options.zoomAnimation,
			touchZoom: !this.options.zoomLevelFixed,
			scrollWheelZoom: !this.options.zoomLevelFixed,
			doubleClickZoom: !this.options.zoomLevelFixed,
			boxZoom: !this.options.zoomLevelFixed,
		});
		this._miniMap.addLayer(this._layer);

		/*Curious workaround: For some reason (possibly the DOM not being completely set up so that the map window
		* is not actually drawn yet?) if you set the view here the minimap window will not manage
		* to calculate tile positions properly and you get a corrupted map. Defer this one millisecond so that this
		* method finishes and the DOM catches up, and it works fine. */
		setTimeout(L.Util.bind(function ()
			{
				this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
			}, this), 1);

		//These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
		this._mainMapMoving = false;
		this._miniMapMoving = false;

		this._mainMap.on('moveend', this._onMainMapMoved, this);
		this._miniMap.on('moveend', this._onMiniMapMoved, this);

		return this._container;
	},

	onRemove: function (map) {
		map.off('moveend', this._onMainMapMoved)
	},

	_onMainMapMoved: function (e) {
		if (!this._miniMapMoving) {
			this._mainMapMoving = true;
			this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
		} else {
			this._miniMapMoving = false;
		}
	},

	_onMiniMapMoved: function (e) {
	if (!this._mainMapMoving) {
			this._miniMapMoving = true;
			this._mainMap.setView(this._miniMap.getCenter(), this._decideZoom(false));
		} else {
			this._mainMapMoving = false;
		}
	},

	_decideZoom: function (fromMaintoMini) {
		if (!this.options.zoomLevelFixed) {
			if (fromMaintoMini)
				return this._mainMap.getZoom() + this.options.zoomLevelOffset;
			else
				return this._miniMap.getZoom() - this.options.zoomLevelOffset;
		} else {
			if (fromMaintoMini)
				return this.options.zoomLevelFixed;
			else
				return this._mainMap.getZoom();
		}
	}

});

L.Map.mergeOptions({
	miniMapControl: false
});

L.Map.addInitHook(function () {
	if (this.options.miniMapControl) {
		this.miniMapControl = (new L.Control.MiniMap()).addTo(this);
	}
});

L.control.minimap = function (options) {
	return new L.Control.MiniMap(options);
};
<commit_msg>Added Aiming rectangle<commit_after>L.Control.MiniMap = L.Control.extend({
	options: {
		position: 'bottomright',
		zoomLevelOffset: -5,
		zoomLevelFixed: false,
		zoomAnimation: false
	},
	//layer is the map layer to be shown in the minimap
	initialize: function (layer, options) {
		L.Util.setOptions(this, options);
		this._layer = layer;
	},

	onAdd: function (map) {

		this._mainMap = map;

		//Creating the container and stopping events from spilling through to the main map.
		this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
		L.DomEvent.disableClickPropagation(this._container);
		L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);

		this._miniMap = new L.Map(this._container,
		{
			attributionControl: false,
			zoomControl: false,
			zoomAnimation: this.options.zoomAnimation,
			touchZoom: !this.options.zoomLevelFixed,
			scrollWheelZoom: !this.options.zoomLevelFixed,
			doubleClickZoom: !this.options.zoomLevelFixed,
			boxZoom: !this.options.zoomLevelFixed,
		});
		this._miniMap.addLayer(this._layer);

		/*Curious workaround: For some reason (possibly the DOM not being completely set up so that the map window
		* is not actually drawn yet?) if you set the view here the minimap window will not manage
		* to calculate tile positions properly and you get a corrupted map. Defer this one millisecond so that this
		* method finishes and the DOM catches up, and it works fine. */
		setTimeout(L.Util.bind(function ()
			{
				this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
				this._aimingRect = L.rectangle(this._mainMap.getBounds(), {color: "#ff7800", weight: 1}).addTo(this._miniMap);
			}, this), 1);

		//These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
		this._mainMapMoving = false;
		this._miniMapMoving = false;

		this._mainMap.on('moveend', this._onMainMapMoved, this);
		this._miniMap.on('moveend', this._onMiniMapMoved, this);

		return this._container;
	},

	onRemove: function (map) {
		map.off('moveend', this._onMainMapMoved)
	},

	_onMainMapMoved: function (e) {
		if (!this._miniMapMoving) {
			this._mainMapMoving = true;
			this._miniMap.setView(this._mainMap.getCenter(), this._decideZoom(true));
		} else {
			this._miniMapMoving = false;
		}
		this._aimingRect.setBounds(this._mainMap.getBounds());
	},

	_onMiniMapMoved: function (e) {
	if (!this._mainMapMoving) {
			this._miniMapMoving = true;
			this._mainMap.setView(this._miniMap.getCenter(), this._decideZoom(false));
		} else {
			this._mainMapMoving = false;
		}
	},

	_decideZoom: function (fromMaintoMini) {
		if (!this.options.zoomLevelFixed) {
			if (fromMaintoMini)
				return this._mainMap.getZoom() + this.options.zoomLevelOffset;
			else
				return this._miniMap.getZoom() - this.options.zoomLevelOffset;
		} else {
			if (fromMaintoMini)
				return this.options.zoomLevelFixed;
			else
				return this._mainMap.getZoom();
		}
	}

});

L.Map.mergeOptions({
	miniMapControl: false
});

L.Map.addInitHook(function () {
	if (this.options.miniMapControl) {
		this.miniMapControl = (new L.Control.MiniMap()).addTo(this);
	}
});

L.control.minimap = function (options) {
	return new L.Control.MiniMap(options);
};
<|endoftext|>


<commit_before>import _ from 'lodash';

const defaults = {
    // HTML node that will become DropZone
    node: null,
    // limit of files within store
    maxFiles: 5,
    // limit total size of files (314572800 === 300mb)
    maxSize: 314572800,
    // a whitelist of file types to validate against
    whitelist: [],
    // file enters window
    windowEnter: function () {},
    // file leaves window
    windowLeave: function () {},
    // drop on the window outside of the DropZone
    windowDrop: function () {},
    // drop on the DropZone
    dropZoneDrop: function () {},
    dropZoneEnter: function () {},
    // dragged files left DropZone
    dropZoneLeave: function () {},
    // reject files
    filesRejected: function () {}
};

export default class DropZone {
    constructor (options) {
        this.options = DropZone.createOptions(defaults, options);
        // ensure we've got integers here, there is a chance these will come
        // in as strings from a DOM node's attributes
        this.options.maxFiles = parseInt(this.options.maxFiles);
        this.options.maxSize = parseInt(this.options.maxSize);
        // cache methods with context
        this.handleDropWithContext = this.handleDrop.bind(this);
        this.handleEnterWithContext = this.handleEnter.bind(this);
        this.handleLeaveWithContext = this.handleLeave.bind(this);
        this.handleWindowEnterWithContext = this.handleWindowEnter.bind(this);
        this.handleWindowLeaveWithContext = this.handleWindowLeave.bind(this);
        this.updateEventTrackerWithContext = this.updateEventTracker.bind(this);
        // a place to be used extenally as a instance based cache
        this.data = {};

        this.setup();
    }

    setup () {
        this.node = DropZone.getVanillaNode(window, this.options.node);

        if (!this.node) {
            throw new Error('DropZone requires a DOM node');
        }

        this.eventPool = [];
        this.files = [];
        this.eventTracker = { node: {}, window: {} };
        this.size = 0;
        this.windowActive = false;
        this.dropZoneActive = false;
        this.customValidation = this.options.validation ? new RegExp(this.options.validation, 'g') : false;

        this.trackEvents('dragenter dragleave');
        this.attachMultipleListeners('drag dragstart dragend dragover dragenter dragleave drop', this.preventer);
        this.registerEvents();
    }

    /**
     * Register drag & drop events
     */
    registerEvents () {
        this.addEventAndStore(this.node, 'dragenter', this.handleEnterWithContext);
        this.addEventAndStore(this.node, 'dragleave', this.handleLeaveWithContext);
        this.addEventAndStore(window, 'dragenter', this.handleWindowEnterWithContext);
        this.addEventAndStore(window, 'dragleave', this.handleWindowLeaveWithContext);
        this.addEventAndStore(window, 'drop', this.handleDropWithContext);
    }

    /**
     * Track multiple events on the winow and DropZone
     * @param  {String} events
     */
    trackEvents (events) {
        events.split(' ').forEach(event => {
            const updateWindow = this.updateEventTracker.bind(this, 'window', event),
                updateNode = this.updateEventTracker.bind(this, 'node', event);

            // initaite event trackers with 0
            this.eventTracker.window[event] = 0;
            this.eventTracker.node[event] = 0;

            // add window events to pool and attach handler
            this.addEventToPool(window, event, updateWindow);
            this.addEventAndStore(window, event, updateWindow);
            // add DropZone node events to pool and attach handler
            this.addEventToPool(this.node, event, updateNode);
            this.addEventAndStore(this.node, event, updateNode);
        });
    }

    /**
     * Update a tracked event
     * @param  {String} id
     * @param  {String} event
     */
    updateEventTracker (id, event) {
        this.eventTracker[id][event]++;
    }

    /**
     * Handle drop event. Checks to see if we can add more files to the files store
     * @param  {Event} event
     */
    handleDrop (event) {
        const { files } = event.dataTransfer,
            dataLength = files ? files.length : 0;

        // determine where the drop has taken place
        // dropped on the dropzone
        if (this.windowActive && this.dropZoneActive) {
            // determine if we can add files to the DropZone, exceptions are handled
            // inside canAcceptFiles
            if (this.canAcceptFiles(files)) {
                for (let index = 0; index < dataLength; index++) {
                    // determine if we can add this file to the DropZone, exceptions are
                    // handled in canAcceptFile
                    if (this.canAcceptFile(files[index])) {
                        this.files.push(this.processFile(files[index]));
                    }
                }
                this.createCallback(this.options.dropZoneDrop,
                    { files: this.getFiles(), node: this.node }
                );
            }
        // dropped on the window
        } else if (this.windowActive && !this.dropZoneActive) {
            this.createCallback(this.options.windowDrop);
        }

        // reset dropzone state
        this.windowActive = false;
        this.dropZoneActive = false;
        this.resetEventTracker();
    }

    /**
     * Determine if a file is currently on the window
     */
    fileOnWindow () {
        const { dragenter, dragleave } = this.eventTracker.window;
        return dragenter > dragleave;
    }

    /**
     * Determin if a file is in the DropZone
     */
    fileOnDropZone () {
        const { dragenter, dragleave } = this.eventTracker.node;
        return dragenter > dragleave;
    }

    /**
     * Handle a file dragged into the window
     */
    handleWindowEnter () {
        if (this.fileOnWindow() && !this.windowActive) {
            this.createCallback(this.options.windowEnter);
            this.windowActive = true;
        }
    }

    /**
     * Handle a file being dragged off of the window
     */
    handleWindowLeave () {
        if (!this.fileOnWindow() && this.windowActive && !this.dropZoneActive) {
            this.createCallback(this.options.windowLeave);
            this.windowActive = false;
        }
    }

    /**
     * Handle drag entering the DropZone
     */
    handleEnter () {
        if (this.fileOnDropZone() && !this.dropZoneActive) {
            this.createCallback(this.options.dropZoneEnter);
            this.dropZoneActive = true;
        }
    }

    /**
     * Handle drag leaving the DropZone
     */
    handleLeave () {
        if (!this.fileOnDropZone() && this.dropZoneActive) {
            this.createCallback(this.options.dropZoneLeave);
            this.dropZoneActive = false;
        }
    }

    /**
     * Determine if the DropZone can accept files
     * @param  {FileList} files
     * @return {Boolean}
     */
    canAcceptFiles (files) {
        const newSize = [...files].reduce((total, file) => total + file.size, 0);

        if (this.getFiles().length + files.length > this.options.maxFiles) {
            this.rejectFiles(0);
            return false;
        } else if (this.size + newSize > this.options.maxSize) {
            this.rejectFiles(1);
            return false;
        } else {
            return true;
        }
    }

    canAcceptFile (file) {
        const whitelistLength = this.options.whitelist.length;

        // console.log('whitelist: ', this.options.whitelist)
        // console.log('type: ', file.type)

        if (whitelistLength) {
            for (let i = 0; i < whitelistLength; i++) {
                if (file.type.search(this.options.whitelist[i]) >= 0) {
                    return true;
                } else {
                    this.rejectFiles(2, file);
                    return false;
                }
            }
        } else {
            return true;
        }
    }

    /**
     * A place we can do something with a file before we add it to the store.
     * @param  {Object} file
     * @return {Object}
     */
    processFile (file) {
        this.size += file.size;
        return {
            file,
            thumbnail: DropZone.getFileThumbnail(file),
            id: _.uniqueId('DropZone_file_'),
            name: DropZone.getFileName(file),
            type: DropZone.getFileType(file),
            size: DropZone.getFileSize(file)
        };
    }

    /**
     * Handle a file rejection with error codes
     * - 0 = the maximum number of files has been exceeded
     * - 1 = the combined size of all files exceeds the maximum file size
     * - 2 = a file has failed the custom validation rule
     */
    rejectFiles (status) {
        let error;

        switch (status) {
            case 0:
                error = `Maximum of ${this.options.maxFiles} files exceeded`;
                break;
            case 1:
                error = `Maximum file size ${DropZone.formatBytes(this.options.maxSize)} exceeded`;
                break;
            case 2:
                error = 'File type not supported';
                break;
        }

        this.createCallback(this.options.filesRejected, { status, error, node: this.node });
    }

    /**
     * Return all files in the store
     * @return {Array}
     */
    getFiles () {
        return this.files;
    }

    /**
     * Remove a file from the store using it's ID
     * @param  {String} id
     */
    removeFile (id) {
        this.files = this.files.filter(file => {
            if (file.id === id) {
                this.size -= file.file.size;
            } else {
                return file;
            }
        });
    }

    /**
     * Check for a callback, if it exists, call it
     * @param  {Function} callback
     * @param  {Object}   data
     */
    createCallback (callback, data = {}) {
        data = _.extend({}, data, { instance: this });

        if (typeof callback === 'function') {
            callback(data);
        }
    }

    /**
     * Attach multiple events
     * @param  {String} events
     * @param  {Function} handler
     */
    attachMultipleListeners (events, handler) {
        events.split(' ').forEach(event => {
            this.addEventAndStore(this.node, event, handler);
            this.addEventAndStore(window, event, handler);
        });
    }

    /**
     * Add event to event pool, the event pool is used to keep track of
     * bound events to allow us to properly remove them if needed
     * @param {HTMLElement} node
     * @param {Event} event
     * @param {Function} handler
     */
    addEventToPool (node, event, handler) {
        this.eventPool.push({ node, event, handler });
    }

    /**
     * Attach an event listener to a node and store in eventPool
     * @param {HTMLElement} node
     * @param {String} event
     * @param {Function} handler
     */
    addEventAndStore (node, event, handler) {
        this.addEventToPool(node, event, handler);
        node.addEventListener(event, handler);
    }

    /**
     * Remove any events that have been attached by DropZone
     */
    removeAllEvents () {
        this.eventPool = this.eventPool.reduce((pool, item) => {
            const { node, event, handler } = item;

            node.removeEventListener(event, handler);
            return pool;
        }, []);
    }

    /**
     * Prevent default event behaviour & propagation
     * @param  {Event} event
     */
    preventer (event) {
        event.preventDefault();
    }

    /**
     * Reset
     * - Remove any events attached by DropZone
     * - re-intialise default values, and event handlers
     */
    reset () {
        this.removeAllEvents();
        this.setup();
    }

    /**
     * Reset event tracker counters
     */
    resetEventTracker () {
        Object.keys(this.eventTracker).forEach(node => {
            Object.keys(this.eventTracker[node]).forEach(event => {
                this.eventTracker[node][event] = 0;
            });
        });
    }

    /**
     * Return a vanilla node from a jQuery
     * @param {Object} window
     * @param {Element} node
     * @returns {Element}
     */
    static getVanillaNode (window, node) {
        return window.$ && node instanceof window.$ ? node[0] : node;
    }

    /**
     * Format filename for printing
     * @param  {Object} file
     * @return {String}
     */
    static getFileName (file) {
        // strip out any directory path in our filename
        return file.name.replace(/.*[\\\/]/, '');
    }

    /**
     * Format type for printing
     * @param  {Object} file
     * @return {String}
     */
    static getFileType (file) {
        return file.type;
    }

    /**
     * Format size for printing
     * @param  {Object} file
     * @return {String}
     */
    static getFileSize (file) {
        return DropZone.formatBytes(file.size);
    }

    /**
     * Create url for image preview
     * @param  {Object} file
     * @return {String|Boolean}
     */
    static getFileThumbnail (file) {
        if (file.type.match(/\/(gif|jpeg|png|svg+xml|svg)/) && URL.createObjectURL) {
            return URL.createObjectURL(file);
        } else {
            return false;
        }
    }

    /**
     * Merge defaults with options
     * @param  {Object} options
     * @return {Object}
     */
    static createOptions (defaults, options) {
        if (typeof Object.assign !== 'function') {
            return _.extend({}, defaults, options);
        } else {
            return Object.assign({}, defaults, options);
        }
    }

    /**
     * Format bytes as unit relative to amount of bytes
     * @param  {Number} bytes
     * @param  {Number} decimal
     * @return {String}
     */
    static formatBytes (bytes, decimal = 1) {
        if (!bytes) {
            return '0 Byte';
        }

        const kb = 1000,
            sizes = ['Bytes', 'KB', 'MB', 'GB'],
            i = Math.floor(Math.log(bytes) / Math.log(kb));
        return `${parseFloat((bytes / Math.pow(kb, i)).toFixed(decimal))} ${sizes[i]}`;
    }
}
<commit_msg>add a default file type for instances where it is empty, this can happen if a file extension is modified after creation<commit_after>import _ from 'lodash';

// todo - reject directories

const defaults = {
    // HTML node that will become DropZone
    node: null,
    // limit of files within store
    maxFiles: 5,
    // limit total size of files (314572800 === 300mb)
    maxSize: 314572800,
    // a whitelist of file types to validate against
    whitelist: [],
    // file enters window
    windowEnter: function () {},
    // file leaves window
    windowLeave: function () {},
    // drop on the window outside of the DropZone
    windowDrop: function () {},
    // drop on the DropZone
    dropZoneDrop: function () {},
    dropZoneEnter: function () {},
    // dragged files left DropZone
    dropZoneLeave: function () {},
    // reject files
    filesRejected: function () {}
};

export default class DropZone {
    constructor (options) {
        this.options = DropZone.createOptions(defaults, options);
        // ensure we've got integers here, there is a chance these will come
        // in as strings from a DOM node's attributes
        this.options.maxFiles = parseInt(this.options.maxFiles);
        this.options.maxSize = parseInt(this.options.maxSize);
        // cache methods with context
        this.handleDropWithContext = this.handleDrop.bind(this);
        this.handleEnterWithContext = this.handleEnter.bind(this);
        this.handleLeaveWithContext = this.handleLeave.bind(this);
        this.handleWindowEnterWithContext = this.handleWindowEnter.bind(this);
        this.handleWindowLeaveWithContext = this.handleWindowLeave.bind(this);
        this.updateEventTrackerWithContext = this.updateEventTracker.bind(this);
        // a place to be used extenally as a instance based cache
        this.data = {};

        this.setup();
    }

    setup () {
        this.node = DropZone.getVanillaNode(window, this.options.node);

        if (!this.node) {
            throw new Error('DropZone requires a DOM node');
        }

        this.eventPool = [];
        this.files = [];
        this.eventTracker = { node: {}, window: {} };
        this.size = 0;
        this.windowActive = false;
        this.dropZoneActive = false;
        this.customValidation = this.options.validation ? new RegExp(this.options.validation, 'g') : false;

        this.trackEvents('dragenter dragleave');
        this.attachMultipleListeners('drag dragstart dragend dragover dragenter dragleave drop', this.preventer);
        this.registerEvents();
    }

    /**
     * Register drag & drop events
     */
    registerEvents () {
        this.addEventAndStore(this.node, 'dragenter', this.handleEnterWithContext);
        this.addEventAndStore(this.node, 'dragleave', this.handleLeaveWithContext);
        this.addEventAndStore(window, 'dragenter', this.handleWindowEnterWithContext);
        this.addEventAndStore(window, 'dragleave', this.handleWindowLeaveWithContext);
        this.addEventAndStore(window, 'drop', this.handleDropWithContext);
    }

    /**
     * Track multiple events on the winow and DropZone
     * @param  {String} events
     */
    trackEvents (events) {
        events.split(' ').forEach(event => {
            const updateWindow = this.updateEventTracker.bind(this, 'window', event),
                updateNode = this.updateEventTracker.bind(this, 'node', event);

            // initaite event trackers with 0
            this.eventTracker.window[event] = 0;
            this.eventTracker.node[event] = 0;

            // add window events to pool and attach handler
            this.addEventToPool(window, event, updateWindow);
            this.addEventAndStore(window, event, updateWindow);
            // add DropZone node events to pool and attach handler
            this.addEventToPool(this.node, event, updateNode);
            this.addEventAndStore(this.node, event, updateNode);
        });
    }

    /**
     * Update a tracked event
     * @param  {String} id
     * @param  {String} event
     */
    updateEventTracker (id, event) {
        this.eventTracker[id][event]++;
    }

    /**
     * Handle drop event. Checks to see if we can add more files to the files store
     * @param  {Event} event
     */
    handleDrop (event) {
        const { files } = event.dataTransfer,
            dataLength = files ? files.length : 0;

        // determine where the drop has taken place
        // dropped on the dropzone
        if (this.windowActive && this.dropZoneActive) {
            // determine if we can add files to the DropZone, exceptions are handled
            // inside canAcceptFiles
            if (this.canAcceptFiles(files)) {
                for (let index = 0; index < dataLength; index++) {
                    // determine if we can add this file to the DropZone, exceptions are
                    // handled in canAcceptFile
                    if (this.canAcceptFile(files[index])) {
                        this.files.push(this.processFile(files[index]));
                    }
                }
                this.createCallback(this.options.dropZoneDrop,
                    { files: this.getFiles(), node: this.node }
                );
            }
        // dropped on the window
        } else if (this.windowActive && !this.dropZoneActive) {
            this.createCallback(this.options.windowDrop);
        }

        // reset dropzone state
        this.windowActive = false;
        this.dropZoneActive = false;
        this.resetEventTracker();
    }

    /**
     * Determine if a file is currently on the window
     */
    fileOnWindow () {
        const { dragenter, dragleave } = this.eventTracker.window;
        return dragenter > dragleave;
    }

    /**
     * Determin if a file is in the DropZone
     */
    fileOnDropZone () {
        const { dragenter, dragleave } = this.eventTracker.node;
        return dragenter > dragleave;
    }

    /**
     * Handle a file dragged into the window
     */
    handleWindowEnter () {
        if (this.fileOnWindow() && !this.windowActive) {
            this.createCallback(this.options.windowEnter);
            this.windowActive = true;
        }
    }

    /**
     * Handle a file being dragged off of the window
     */
    handleWindowLeave () {
        if (!this.fileOnWindow() && this.windowActive && !this.dropZoneActive) {
            this.createCallback(this.options.windowLeave);
            this.windowActive = false;
        }
    }

    /**
     * Handle drag entering the DropZone
     */
    handleEnter () {
        if (this.fileOnDropZone() && !this.dropZoneActive) {
            this.createCallback(this.options.dropZoneEnter);
            this.dropZoneActive = true;
        }
    }

    /**
     * Handle drag leaving the DropZone
     */
    handleLeave () {
        if (!this.fileOnDropZone() && this.dropZoneActive) {
            this.createCallback(this.options.dropZoneLeave);
            this.dropZoneActive = false;
        }
    }

    /**
     * Determine if the DropZone can accept files
     * @param  {FileList} files
     * @return {Boolean}
     */
    canAcceptFiles (files) {
        const newSize = [...files].reduce((total, file) => total + file.size, 0);

        if (this.getFiles().length + files.length > this.options.maxFiles) {
            this.rejectFiles(0);
            return false;
        } else if (this.size + newSize > this.options.maxSize) {
            this.rejectFiles(1);
            return false;
        } else {
            return true;
        }
    }

    canAcceptFile (file) {
        const whitelistLength = this.options.whitelist.length;

        // console.log('whitelist: ', this.options.whitelist)
        // console.log('type: ', file.type)

        if (whitelistLength) {
            for (let i = 0; i < whitelistLength; i++) {
                if (file.type.search(this.options.whitelist[i]) >= 0) {
                    return true;
                } else {
                    this.rejectFiles(2, file);
                    return false;
                }
            }
        } else {
            return true;
        }
    }

    /**
     * A place we can do something with a file before we add it to the store.
     * @param  {Object} file
     * @return {Object}
     */
    processFile (file) {
        this.size += file.size;
        return {
            file,
            thumbnail: DropZone.getFileThumbnail(file),
            id: _.uniqueId('DropZone_file_'),
            name: DropZone.getFileName(file),
            type: DropZone.getFileType(file),
            size: DropZone.getFileSize(file)
        };
    }

    /**
     * Handle a file rejection with error codes
     * - 0 = the maximum number of files has been exceeded
     * - 1 = the combined size of all files exceeds the maximum file size
     * - 2 = a file has failed the custom validation rule
     */
    rejectFiles (status) {
        let error;

        switch (status) {
            case 0:
                error = `Maximum of ${this.options.maxFiles} file${this.options.maxFiles > 1 ? 's' : ''} exceeded`;
                break;
            case 1:
                error = `Maximum file size ${DropZone.formatBytes(this.options.maxSize)} exceeded`;
                break;
            case 2:
                error = 'File type not supported';
                break;
        }

        this.createCallback(this.options.filesRejected, { status, error, node: this.node });
    }

    /**
     * Return all files in the store
     * @return {Array}
     */
    getFiles () {
        return this.files;
    }

    /**
     * Remove a file from the store using it's ID
     * @param  {String} id
     */
    removeFile (id) {
        this.files = this.files.filter(file => {
            if (file.id === id) {
                this.size -= file.file.size;
            } else {
                return file;
            }
        });
    }

    /**
     * Check for a callback, if it exists, call it
     * @param  {Function} callback
     * @param  {Object}   data
     */
    createCallback (callback, data = {}) {
        data = _.extend({}, data, { instance: this });

        if (typeof callback === 'function') {
            callback(data);
        }
    }

    /**
     * Attach multiple events
     * @param  {String} events
     * @param  {Function} handler
     */
    attachMultipleListeners (events, handler) {
        events.split(' ').forEach(event => {
            this.addEventAndStore(this.node, event, handler);
            this.addEventAndStore(window, event, handler);
        });
    }

    /**
     * Add event to event pool, the event pool is used to keep track of
     * bound events to allow us to properly remove them if needed
     * @param {HTMLElement} node
     * @param {Event} event
     * @param {Function} handler
     */
    addEventToPool (node, event, handler) {
        this.eventPool.push({ node, event, handler });
    }

    /**
     * Attach an event listener to a node and store in eventPool
     * @param {HTMLElement} node
     * @param {String} event
     * @param {Function} handler
     */
    addEventAndStore (node, event, handler) {
        this.addEventToPool(node, event, handler);
        node.addEventListener(event, handler);
    }

    /**
     * Remove any events that have been attached by DropZone
     */
    removeAllEvents () {
        this.eventPool = this.eventPool.reduce((pool, item) => {
            const { node, event, handler } = item;

            node.removeEventListener(event, handler);
            return pool;
        }, []);
    }

    /**
     * Prevent default event behaviour & propagation
     * @param  {Event} event
     */
    preventer (event) {
        event.preventDefault();
    }

    /**
     * Reset
     * - Remove any events attached by DropZone
     * - re-intialise default values, and event handlers
     */
    reset () {
        this.removeAllEvents();
        this.setup();
    }

    /**
     * Reset event tracker counters
     */
    resetEventTracker () {
        Object.keys(this.eventTracker).forEach(node => {
            Object.keys(this.eventTracker[node]).forEach(event => {
                this.eventTracker[node][event] = 0;
            });
        });
    }

    /**
     * Return a vanilla node from a jQuery
     * @param {Object} window
     * @param {Element} node
     * @returns {Element}
     */
    static getVanillaNode (window, node) {
        return window.$ && node instanceof window.$ ? node[0] : node;
    }

    /**
     * Format filename for printing
     * @param  {Object} file
     * @return {String}
     */
    static getFileName (file) {
        // strip out any directory path in our filename
        return file.name.replace(/.*[\\\/]/, '');
    }

    /**
     * Format type for printing
     * @param  {Object} file
     * @return {String}
     */
    static getFileType (file) {
        if (file.type.length) {
            return file.type;
        } else {
            return 'application/file';
        }
    }

    /**
     * Format size for printing
     * @param  {Object} file
     * @return {String}
     */
    static getFileSize (file) {
        return DropZone.formatBytes(file.size);
    }

    /**
     * Create url for image preview
     * @param  {Object} file
     * @return {String|Boolean}
     */
    static getFileThumbnail (file) {
        if (file.type.match(/\/(gif|jpeg|png|svg+xml|svg)/) && URL.createObjectURL) {
            return URL.createObjectURL(file);
        } else {
            return false;
        }
    }

    /**
     * Merge defaults with options
     * @param  {Object} options
     * @return {Object}
     */
    static createOptions (defaults, options) {
        if (typeof Object.assign !== 'function') {
            return _.extend({}, defaults, options);
        } else {
            return Object.assign({}, defaults, options);
        }
    }

    /**
     * Format bytes as unit relative to amount of bytes
     * @param  {Number} bytes
     * @param  {Number} decimal
     * @return {String}
     */
    static formatBytes (bytes, decimal = 1) {
        if (!bytes) {
            return '0 Byte';
        }

        const kb = 1000,
            sizes = ['Bytes', 'KB', 'MB', 'GB'],
            i = Math.floor(Math.log(bytes) / Math.log(kb));
        return `${parseFloat((bytes / Math.pow(kb, i)).toFixed(decimal))} ${sizes[i]}`;
    }
}
<|endoftext|>