<commit_before>import React, { Component } from 'react';

import MarkedRanges from './MarkedRanges';
import { markEmail, markUrls } from './markers';
import { combineValidators } from './presets';
import { foldWhitespace, KEY_CODES } from './strings';
import { filteredMap, makeSelectionRange, positiveNumericPropCheck } from './utils';
import { makeValidators, validatorDefaults, validatorsPropCheck } from './validators';

const
    getFromRxCache = (function() {
        const cache = {};
        function getFromRxCache(str) {
            if (str in cache) {
                return cache[str];
            }
            return cache[str] = new RegExp(str);
        }
        return getFromRxCache;
    })(),
    noOp = function(){};

export const classNames = {
    email: 'txr-email',
    nonDigits: 'txr-non-digit txr-invalid',
    nonEmail: 'txr-non-email txr-invalid',
    nonInt: 'txr-non-int txr-invalid',
    nonNumeric: 'txr-non-numeric txr-invalid',
    nonUint: 'txr-non-uint txr-invalid',
    nonUrl: 'txr-non-url txr-invalid',
    tooLong: 'txr-too-long txr-invalid',
    url: 'txr-url'
};

export default class TxRegionsInput extends Component {

    constructor(props) {
        super(props);
        const
            initialValue = props.defaultValue || props.value || '',
            validators = combineValidators(props.validators, props.preset);
        this.state = {
            clean: '',
            cleanCaretPos: 0,
            hasFocus: false,
            raw: initialValue,
            rawCaretPos: 0
        };

        this._valueHistory = [initialValue];
        this._caretHistory = [0];
        this._historyPos = 0;
        this._elm = undefined;
        this._input = undefined;
        // for checking if the content has changed
        this._txContent = undefined;
        // track number of descendants bc <enter> causes

        // to be inserted on [contenteditable] elements but does not affect
        // the `textContent` of the [contenteditable] element
        this._numDescendants = undefined;
        this._marker = new MarkedRanges();
        this._markFn = [this._marker.markRange.bind(this._marker)];
        // ux state related properties
        this._uxInitialValue = initialValue;
        this._uxValueChanged = false;
        this._uxHadFocus = false;
        // validity related
        if (validators) {
            this._validators = makeValidators(validators, props.invalidClassName);
        } else {
            this._validators = undefined;
        }
        this._violations = undefined;
        // cache the foldWhitespace processing of state.raw
        this._cleanCacheSrc = undefined;
        this._cleanCacheValue = undefined;
    }

    get clean() {
        if (this.state.raw === this._cleanCacheSrc) {
            return this._cleanCacheValue;
        }
        this._cleanCacheSrc = this.state.raw;
        return this._cleanCacheValue = foldWhitespace(this._cleanCacheSrc);
    }

    get raw() {
        return this.state.raw;
    }

    _stepBackInHistory() {
        const
            max = this._valueHistory.length - 1,
            raw = this.state.raw,
            caret = this.state.rawCaretPos;
        var r,
            c;
        while (this._historyPos < max) {
            this._historyPos++;
            r = this._valueHistory[this._historyPos];
            c = this._caretHistory[this._historyPos];
            if (r !== raw || c !== caret) {
                this._setRaw(r, c, true);
                return;
            }
        }
    }

    _stepForwardInHistory() {
        const
            raw = this.state.raw,
            caret = this.state.rawCaretPos;
        var r,
            c;
        while (this._historyPos > 0) {
            this._historyPos--;
            r = this._valueHistory[this._historyPos];
            c = this._caretHistory[this._historyPos];
            if (r !== raw || c !== caret) {
                this._setRaw(r, c, true);
                return;
            }
        }
    }

    _addToHistory(value, caret) {
        const maxHistory = +this.props.maxHistory;
        if (this._historyPos) {
            // have already stepped back in history, so need to clear
            // a bit of history before adding to the history
            this._valueHistory.splice(0, this._historyPos);
            this._caretHistory.splice(0, this._historyPos);
            this._historyPos = 0;
        }
        this._valueHistory.unshift(value);
        this._caretHistory.unshift(caret);
        if (maxHistory && this._valueHistory.length > maxHistory) {
            this._valueHistory.length = maxHistory;
            this._caretHistory.length = maxHistory;
        }
    }

    _updateViolations(markRange) {
        const
            clean = this.clean,
            max = +this.props.maxLength,
            min = +this.props.minLength,
            pattern = this.props.pattern,
            isRequired = !!this.props.required,
            shouldMark = !('markInvalid' in this.props) || !!this.props.markInvalid ? true : undefined,
            invalidClassName = this.props.invalidClassName || validatorDefaults.className;

        let value = this._validators ? this._validators.exec(clean, shouldMark && markRange) : '';

        if (max && max > 0 && clean.length > max) {
            value += ' maxLength';
            if (shouldMark && markRange) {
                markRange(max, clean.length, invalidClassName);
            }
        }
        if (min && min > 0 && min > clean.length) {
            value += ' minLength';
            if (shouldMark && markRange) {
                markRange(0, clean.length, invalidClassName);
            }
        }
        if (pattern && clean.replace(getFromRxCache(pattern), '').length > 0) {
            value += ' pattern';
        }
        if (isRequired && !clean) {
            value += ' required';
        }
        this._violations = value;
    }

    _setRaw(value, caret, focus) {
        if (this.props.onRawChange) {
            this.props.onRawChange({
                caret: caret,
                raw: value
            });
        }
        if (!('value' in this.props)) {
            this.setState({
                hasFocus: focus,
                raw: value,
                rawCaretPos: caret
            })
        } else if (!('caret' in this.props)) {
            this.setState({
                hasFocus: focus,
                rawCaretPos: caret
            });
        }
    }

    _findSelectionOffset() {
        var sel = document.getSelection(),
            rng = sel.rangeCount && sel.getRangeAt(0),
            node = rng && rng.endContainer,
            offset = rng && rng.endOffset;

        if (!rng) {
            return undefined;
        }

        if (!this._elm.contains(node)) {
            return 0;
        }
        while (node !== this._elm) {
            while (node.previousSibling) {
                node = node.previousSibling;
                if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
                    offset += node.textContent.length;
                }
            }
            node = node.parentNode;
        }
        return offset;
    }

    _setSelection() {
        var sel = document.getSelection(),
            str = this.state.raw.slice(0, this.state.rawCaretPos),
            cleanCaretPos = foldWhitespace(str).length,
            range = makeSelectionRange(this._elm, cleanCaretPos)

        if (!range) {
            range = document.createRange();
            range.selectNodeContents(this._elm);
            range.collapse(false);
        }
        sel.removeAllRanges();
        sel.addRange(range);
    }

    _forceHtml() {
        const
            clean = this.clean,
            markFn = this._markFn[0];

        let markers = this.props.markers,
            i,
            ci,
            rng,
            tx,
            span,
            rngClasses;

        this._elm.innerHTML = '';
        if (!clean) {
            this._txContent = this._elm.textContent;
            this._numDescendants = this._elm.childNodes.length;
            return '';
        }
        this._marker.reset();
        if (typeof markers === 'function') {
            markers = [markers];
            i = 1;
        } else {
            i = markers ? markers.length : 0;
        }
        while (i--) {
            markers[i](clean, markFn, this, classNames);
        }
        // process the violations -- pass the marker function so the validators
        // can (optionally) mark the invalid text
        this._updateViolations(markFn);

        const
            ranges = this._marker.getRanges(),
            rmax = ranges.length,
            frag = document.createDocumentFragment();

        i = 0;
        ci = 0;
        for (; i < rmax; i++) {
            rng = ranges[i];
            if (rng.start > ci) {
                tx = document.createTextNode(clean.slice(ci, rng.start));
                frag.appendChild(tx);
            }
            span = document.createElement('span');
            rngClasses = rng.classes.join(' ').split(/\s+/g);
            span.classList.add.apply(span.classList, rngClasses);
            span.textContent = clean.slice(rng.start, rng.end);
            frag.appendChild(span);
            ci = rng.end;
        }
        if (ci < clean.length) {
            tx = document.createTextNode(clean.slice(ci));
            frag.appendChild(tx);
        }

        this._elm.appendChild(frag);
        this._txContent = this._elm.textContent;
        this._numDescendants = this._elm.querySelectorAll('*').length;
    }

    _emitUpdate() {
        var data;
        if (typeof this.props.onUpdate !== 'function') {
            return;
        }
        data = {
            clean: this.clean,
            component: this,
            isValid: !this._violations,
            raw: this.state.raw,
            violations: this._violations || undefined
        };
        if (this._input) {
            data.input = this._input;
            data.value = this._input.value;
        }
        this.props.onUpdate(data);
    }

    _handleChange() {
        // if a change occurred, note the new text and caret position
        const
            raw = this._elm.textContent,
            numDescendants = this._elm.querySelectorAll('*').length,
            hasFocus = this._elm === document.activeElement,
            caretPos = this._findSelectionOffset();

        // if nothing changed, ignore this callback
        if (raw === this._txContent && numDescendants === this._numDescendants) {
            return;
        }
        this._uxValueChanged = true;
        // reset the content back to the last known state
        this._forceHtml();
        if (hasFocus) {
            this._setSelection();
        }
        this._setRaw(raw, caretPos, hasFocus);
    }

    _handleFocus() {
        this._uxHadFocus = true;
        // setting the state in the onFocus handler directly clobbers the
        // initial cursor position, so set the state asynchronously
        setTimeout(() => {
            this.setState({
                hasFocus: true,
                rawCaretPos: this._findSelectionOffset()
            }, 0);
        })
    }

    _handleBlur() {
        this.setState({hasFocus: false});
    }

    _handleKeyDown(event) {
        if (event.metaKey && event.keyCode === KEY_CODES.Z) {
            event.preventDefault();
            this._isUndo = true;
            if (event.shiftKey) {
                this._stepForwardInHistory();
            } else {
                this._stepBackInHistory();
            }
        }
    }

    componentDidMount() {
        this._uxInitialValue = this.props.defaultValue || this.props.value || '';
        this.setState({
            raw: this._uxInitialValue,
            rawCaretPos: 0
        });
        if (this._input) {
            this._input.value = this._uxInitialValue;
        }
        this._forceHtml();
        if (this._input) {
            // mark the input as `:invalid` if there are violations
            this._input.setCustomValidity(this._violations || '');
        }
        this._emitUpdate();
    }

    componentWillReceiveProps(nextProps, nextState) {
        if ('value' in nextProps) {
            this.setState({raw: nextProps.value});
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        return (
            nextState.hasFocus !== this.state.hasFocus ||
            nextState.raw !== this.state.raw ||
            nextState.rawCaretPos !== this.state.rawCaretPos ||
            !!this.props.dynamicMarkers && this.props.markers !== nextProps.markers
        );
    }

    componentWillUpdate(nextProps, nextState) {
        this._updateViolations();
    }

    componentDidUpdate() {
        this._forceHtml();

        if (this.state.hasFocus && this.state.rawCaretPos != null) {
            this._setSelection();
        }
        if (this._isUndo) {
            this._isUndo = false;
        } else {
            this._addToHistory(this.state.raw, this.state.hasFocus ? this.state.rawCaretPos : this.state.raw.length);
        }
        if (this._input) {
            // mark the input as `:invalid` if there are violations
            this._input.setCustomValidity(this._violations || '');
        }
        this._emitUpdate();
    }

    render() {
        const
            p = this.props,
            clean = this.clean,
            editableProps = p.setEditableProps || {},
            wrapperProps = p.setWrapperProps || {};

        editableProps.contentEditable = true;

        let uxFocus,
            uxChanged,
            input,
            inputProps;

        uxChanged = this._uxValueChanged ? 'changed-done' : 'changed-never';
        if (this.state.hasFocus) {
            uxFocus = 'focus-now ';
        } else if (this._uxHadFocus) {
            uxFocus = 'focus-had ';
        } else {
            uxFocus = 'focus-never ';
        }

        if (p.input) {
            inputProps = p.setInputProps || {};
            input = (
                // Use input type="text" so can still manage the validity
                // (`:invalid`) manually (type="hidden" ignore custom validity).
                // The `hidden` attribute takes care of making it invisible. Use
                // a `noOp` onChange handler to get rid of the react warning.
                <input
                    ref={(elm) => this._input = elm}
                    hidden
                    type="text"
                    onChange={noOp}
                    name={typeof p.input === 'string' ? p.input : ''}
                    value={clean.trim()}
                    {...inputProps} />
            );
        }
        return (

                {input}

 this._elm = elm}
                    onBlur={this._handleBlur.bind(this)}
                    onFocus={this._handleFocus.bind(this)}
                    onInput={this._handleChange.bind(this)}
                    onKeyDown={this._handleKeyDown.bind(this)}
                    data-placeholder={clean ? '' : p.placeholder}
                    {...editableProps} />
                {p.children}

        );
    }
}

const PropTypes = React.PropTypes;

TxRegionsInput.propTypes = {
    defaultValue: PropTypes.string,
    dynamicMarkers: React.PropTypes.bool,
    input: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
    invalidClassName: PropTypes.string,
    markers: PropTypes.oneOfType([PropTypes.func, PropTypes.array]),
    markInvalid: PropTypes.bool,
    maxHistory: positiveNumericPropCheck,
    maxLength: positiveNumericPropCheck,
    minLength: positiveNumericPropCheck,
    onRawChange: PropTypes.func,
    onUpdate: PropTypes.func,
    placeholder: PropTypes.string,
    required: PropTypes.bool,
    setEditableProps: PropTypes.object,
    setInputProps: PropTypes.object,
    setWrapperProps: PropTypes.object,
    validators: validatorsPropCheck,
    value: React.PropTypes.string,
};

TxRegionsInput.defaultProps = {
    defaultValue: '',
    markers: undefined,
    maxHistory: 100,
    placeholder: '',
};

TxRegionsInput.defaultMarkers = TxRegionsInput.defaultProps.markers;
<commit_msg>spell check and better data-violations updating<commit_after>import React, { Component } from 'react';

import MarkedRanges from './MarkedRanges';
import { markEmail, markUrls } from './markers';
import { combineValidators } from './presets';
import { foldWhitespace, KEY_CODES } from './strings';
import { filteredMap, makeSelectionRange, positiveNumericPropCheck } from './utils';
import { makeValidators, validatorDefaults, validatorsPropCheck } from './validators';

const
    getFromRxCache = (function() {
        const cache = {};
        function getFromRxCache(str) {
            if (str in cache) {
                return cache[str];
            }
            return cache[str] = new RegExp(str);
        }
        return getFromRxCache;
    })(),
    noOp = function(){};

export const classNames = {
    email: 'txr-email',
    nonDigits: 'txr-non-digit txr-invalid',
    nonEmail: 'txr-non-email txr-invalid',
    nonInt: 'txr-non-int txr-invalid',
    nonNumeric: 'txr-non-numeric txr-invalid',
    nonUint: 'txr-non-uint txr-invalid',
    nonUrl: 'txr-non-url txr-invalid',
    tooLong: 'txr-too-long txr-invalid',
    url: 'txr-url'
};

export default class TxRegionsInput extends Component {

    constructor(props) {
        super(props);
        const
            initialValue = props.defaultValue || props.value || '',
            validators = combineValidators(props.validators, props.preset);
        this.state = {
            clean: '',
            cleanCaretPos: 0,
            hasFocus: false,
            raw: initialValue,
            rawCaretPos: 0
        };

        this._valueHistory = [initialValue];
        this._caretHistory = [0];
        this._historyPos = 0;
        this._wrapper = undefined;
        this._elm = undefined;
        this._input = undefined;
        // for checking if the content has changed
        this._txContent = undefined;
        // track number of descendants bc <enter> causes

        // to be inserted on [contenteditable] elements but does not affect
        // the `textContent` of the [contenteditable] element
        this._numDescendants = undefined;
        this._marker = new MarkedRanges();
        this._markFn = [this._marker.markRange.bind(this._marker)];
        // ux state related properties
        this._uxInitialValue = initialValue;
        this._uxValueChanged = false;
        this._uxHadFocus = false;
        // validity related
        if (validators) {
            this._validators = makeValidators(validators, props.invalidClassName);
        } else {
            this._validators = undefined;
        }
        this._violations = undefined;
        // cache the foldWhitespace processing of state.raw
        this._cleanCacheSrc = undefined;
        this._cleanCacheValue = undefined;
    }

    get clean() {
        if (this.state.raw === this._cleanCacheSrc) {
            return this._cleanCacheValue;
        }
        this._cleanCacheSrc = this.state.raw;
        return this._cleanCacheValue = foldWhitespace(this._cleanCacheSrc);
    }

    get raw() {
        return this.state.raw;
    }

    _stepBackInHistory() {
        const
            max = this._valueHistory.length - 1,
            raw = this.state.raw,
            caret = this.state.rawCaretPos;
        var r,
            c;
        while (this._historyPos < max) {
            this._historyPos++;
            r = this._valueHistory[this._historyPos];
            c = this._caretHistory[this._historyPos];
            if (r !== raw || c !== caret) {
                this._setRaw(r, c, true);
                return;
            }
        }
    }

    _stepForwardInHistory() {
        const
            raw = this.state.raw,
            caret = this.state.rawCaretPos;
        var r,
            c;
        while (this._historyPos > 0) {
            this._historyPos--;
            r = this._valueHistory[this._historyPos];
            c = this._caretHistory[this._historyPos];
            if (r !== raw || c !== caret) {
                this._setRaw(r, c, true);
                return;
            }
        }
    }

    _addToHistory(value, caret) {
        const maxHistory = +this.props.maxHistory;
        if (this._historyPos) {
            // have already stepped back in history, so need to clear
            // a bit of history before adding to the history
            this._valueHistory.splice(0, this._historyPos);
            this._caretHistory.splice(0, this._historyPos);
            this._historyPos = 0;
        }
        this._valueHistory.unshift(value);
        this._caretHistory.unshift(caret);
        if (maxHistory && this._valueHistory.length > maxHistory) {
            this._valueHistory.length = maxHistory;
            this._caretHistory.length = maxHistory;
        }
    }

    _updateViolations(markRange) {
        const
            clean = this.clean,
            max = +this.props.maxLength,
            min = +this.props.minLength,
            pattern = this.props.pattern,
            isRequired = !!this.props.required,
            shouldMark = !('markInvalid' in this.props) || !!this.props.markInvalid ? true : undefined,
            invalidClassName = this.props.invalidClassName || validatorDefaults.className;

        let value = this._validators ? this._validators.exec(clean, shouldMark && markRange) : '';

        if (max && max > 0 && clean.length > max) {
            value += ' maxLength';
            if (shouldMark && markRange) {
                markRange(max, clean.length, invalidClassName);
            }
        }
        if (min && min > 0 && min > clean.length) {
            value += ' minLength';
            if (shouldMark && markRange) {
                markRange(0, clean.length, invalidClassName);
            }
        }
        if (pattern && clean.replace(getFromRxCache(pattern), '').length > 0) {
            value += ' pattern';
        }
        if (isRequired && !clean) {
            value += ' required';
        }
        this._violations = value;
    }

    _setRaw(value, caret, focus) {
        if (this.props.onRawChange) {
            this.props.onRawChange({
                caret: caret,
                raw: value
            });
        }
        if (!('value' in this.props)) {
            this.setState({
                hasFocus: focus,
                raw: value,
                rawCaretPos: caret
            })
        } else if (!('caret' in this.props)) {
            this.setState({
                hasFocus: focus,
                rawCaretPos: caret
            });
        }
    }

    _findSelectionOffset() {
        var sel = document.getSelection(),
            rng = sel.rangeCount && sel.getRangeAt(0),
            node = rng && rng.endContainer,
            offset = rng && rng.endOffset;

        if (!rng) {
            return undefined;
        }

        if (!this._elm.contains(node)) {
            return 0;
        }
        while (node !== this._elm) {
            while (node.previousSibling) {
                node = node.previousSibling;
                if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
                    offset += node.textContent.length;
                }
            }
            node = node.parentNode;
        }
        return offset;
    }

    _setSelection() {
        var sel = document.getSelection(),
            str = this.state.raw.slice(0, this.state.rawCaretPos),
            cleanCaretPos = foldWhitespace(str).length,
            range = makeSelectionRange(this._elm, cleanCaretPos)

        if (!range) {
            range = document.createRange();
            range.selectNodeContents(this._elm);
            range.collapse(false);
        }
        sel.removeAllRanges();
        sel.addRange(range);
    }

    _forceHtml() {
        const
            clean = this.clean,
            markFn = this._markFn[0];

        let markers = this.props.markers,
            i,
            ci,
            rng,
            tx,
            span,
            rngClasses;

        this._elm.innerHTML = '';
        if (!clean) {
            this._txContent = this._elm.textContent;
            this._numDescendants = this._elm.childNodes.length;
            return '';
        }
        this._marker.reset();
        if (typeof markers === 'function') {
            markers = [markers];
            i = 1;
        } else {
            i = markers ? markers.length : 0;
        }
        while (i--) {
            markers[i](clean, markFn, this, classNames);
        }
        // process the violations -- pass the marker function so the validators
        // can (optionally) mark the invalid text
        this._updateViolations(markFn);

        const
            ranges = this._marker.getRanges(),
            rmax = ranges.length,
            frag = document.createDocumentFragment();

        i = 0;
        ci = 0;
        for (; i < rmax; i++) {
            rng = ranges[i];
            if (rng.start > ci) {
                tx = document.createTextNode(clean.slice(ci, rng.start));
                frag.appendChild(tx);
            }
            span = document.createElement('span');
            rngClasses = rng.classes.join(' ').split(/\s+/g);
            span.classList.add.apply(span.classList, rngClasses);
            span.textContent = clean.slice(rng.start, rng.end);
            frag.appendChild(span);
            ci = rng.end;
        }
        if (ci < clean.length) {
            tx = document.createTextNode(clean.slice(ci));
            frag.appendChild(tx);
        }

        this._elm.appendChild(frag);
        this._txContent = this._elm.textContent;
        this._numDescendants = this._elm.querySelectorAll('*').length;
        // set the violations
        if (this._violations) {
            this._wrapper.setAttribute('data-violations', this._violations);
        } else {
            this._wrapper.removeAttribute('data-violations');
        }
    }

    _emitUpdate() {
        var data;
        if (typeof this.props.onUpdate !== 'function') {
            return;
        }
        data = {
            clean: this.clean,
            component: this,
            isValid: !this._violations,
            raw: this.state.raw,
            violations: this._violations || undefined
        };
        if (this._input) {
            data.input = this._input;
            data.value = this._input.value;
        }
        this.props.onUpdate(data);
    }

    _handleChange() {
        // if a change occurred, note the new text and caret position
        const
            raw = this._elm.textContent,
            numDescendants = this._elm.querySelectorAll('*').length,
            hasFocus = this._elm === document.activeElement,
            caretPos = this._findSelectionOffset();

        // if nothing changed, ignore this callback
        if (raw === this._txContent && numDescendants === this._numDescendants) {
            return;
        }
        this._uxValueChanged = true;
        // reset the content back to the last known state
        this._forceHtml();
        if (hasFocus) {
            this._setSelection();
        }
        this._setRaw(raw, caretPos, hasFocus);
    }

    _handleFocus() {
        this._uxHadFocus = true;
        // setting the state in the onFocus handler directly clobbers the
        // initial cursor position, so set the state asynchronously
        setTimeout(() => {
            this.setState({
                hasFocus: true,
                rawCaretPos: this._findSelectionOffset()
            }, 0);
        })
    }

    _handleBlur() {
        this.setState({hasFocus: false});
    }

    _handleKeyDown(event) {
        if (event.metaKey && event.keyCode === KEY_CODES.Z) {
            event.preventDefault();
            this._isUndo = true;
            if (event.shiftKey) {
                this._stepForwardInHistory();
            } else {
                this._stepBackInHistory();
            }
        }
    }

    componentDidMount() {
        this._uxInitialValue = this.props.defaultValue || this.props.value || '';
        this.setState({
            raw: this._uxInitialValue,
            rawCaretPos: 0
        });
        if (this._input) {
            this._input.value = this._uxInitialValue;
        }
        this._forceHtml();
        if (this._input) {
            // mark the input as `:invalid` if there are violations
            this._input.setCustomValidity(this._violations || '');
        }
        this._emitUpdate();
    }

    componentWillReceiveProps(nextProps, nextState) {
        if ('value' in nextProps) {
            this.setState({raw: nextProps.value});
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        return (
            nextState.hasFocus !== this.state.hasFocus ||
            nextState.raw !== this.state.raw ||
            nextState.rawCaretPos !== this.state.rawCaretPos ||
            !!this.props.dynamicMarkers && this.props.markers !== nextProps.markers
        );
    }

    componentWillUpdate(nextProps, nextState) {
        this._updateViolations();
    }

    componentDidUpdate() {
        this._forceHtml();

        if (this.state.hasFocus && this.state.rawCaretPos != null) {
            this._setSelection();
        }
        if (this._isUndo) {
            this._isUndo = false;
        } else {
            this._addToHistory(this.state.raw, this.state.hasFocus ? this.state.rawCaretPos : this.state.raw.length);
        }
        if (this._input) {
            // mark the input as `:invalid` if there are violations
            this._input.setCustomValidity(this._violations || '');
        }
        this._emitUpdate();
    }

    render() {
        const
            p = this.props,
            clean = this.clean,
            editableProps = p.setEditableProps || {},
            wrapperProps = p.setWrapperProps || {},
            violations = this._violations ? {'data-violations': this._violations} : {};

        editableProps.contentEditable = true;

        let uxFocus,
            uxChanged,
            input,
            inputProps;

        uxChanged = this._uxValueChanged ? 'changed-done' : 'changed-never';
        if (this.state.hasFocus) {
            uxFocus = 'focus-now ';
        } else if (this._uxHadFocus) {
            uxFocus = 'focus-had ';
        } else {
            uxFocus = 'focus-never ';
        }

        if (p.input) {
            inputProps = p.setInputProps || {};
            input = (
                // Use input type="text" so can still manage the validity
                // (`:invalid`) manually (type="hidden" ignore custom validity).
                // The `hidden` attribute takes care of making it invisible. Use
                // a `noOp` onChange handler to get rid of the react warning.
                <input
                    ref={(elm) => this._input = elm}
                    hidden
                    type="text"
                    onChange={noOp}
                    name={typeof p.input === 'string' ? p.input : ''}
                    value={clean.trim()}
                    {...inputProps} />
            );
        }
        return (

 this._wrapper = elm}
                {...wrapperProps}
                {...violations}
                data-ux-state={uxFocus + uxChanged}
            >
                {input}

 this._elm = elm}
                    onBlur={this._handleBlur.bind(this)}
                    onFocus={this._handleFocus.bind(this)}
                    onInput={this._handleChange.bind(this)}
                    onKeyDown={this._handleKeyDown.bind(this)}
                    data-placeholder={clean ? '' : p.placeholder}
                    spellCheck={p.spellCheck}
                    {...editableProps} />
                {p.children}

        );
    }
}

const PropTypes = React.PropTypes;

TxRegionsInput.propTypes = {
    defaultValue: PropTypes.string,
    dynamicMarkers: React.PropTypes.bool,
    input: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
    invalidClassName: PropTypes.string,
    markers: PropTypes.oneOfType([PropTypes.func, PropTypes.array]),
    markInvalid: PropTypes.bool,
    maxHistory: positiveNumericPropCheck,
    maxLength: positiveNumericPropCheck,
    minLength: positiveNumericPropCheck,
    onRawChange: PropTypes.func,
    onUpdate: PropTypes.func,
    placeholder: PropTypes.string,
    required: PropTypes.bool,
    setEditableProps: PropTypes.object,
    setInputProps: PropTypes.object,
    setWrapperProps: PropTypes.object,
    spellCheck: PropTypes.bool,
    validators: validatorsPropCheck,
    value: React.PropTypes.string,
};

TxRegionsInput.defaultProps = {
    defaultValue: '',
    markers: undefined,
    maxHistory: 100,
    placeholder: '',
    spellCheck: false
};

TxRegionsInput.defaultMarkers = TxRegionsInput.defaultProps.markers;
<|endoftext|>


<commit_before>var canvas = document.getElementsByTagName('canvas')[0];
var ctx = null;
var body = document.getElementsByTagName('body')[0];
var keysDown = new Array();
var SCREENWIDTH  = 640;
var SCREENHEIGHT = 480;
var MODE_TITLE = 0;
var MODE_PLAY  = 1;
var MODE_WIN   = 2;

function getImage(name)
{
    image = new Image();
    image.src = 'graphics/'+name+'.png';
    return image;
}

function drawChar(context, c, x, y)
{
    c = c.charCodeAt(0);
    if(c > 0) {
        context.drawImage(bitfont, c*6, 0, 6,8, x, y, 12, 16);
    }
}

function drawString(context, string, x, y) {
    string = string.toUpperCase();
    for(i = 0; i < string.length; i++) {
	drawChar(context, string[i], x, y);
	x += 12;
    }
}

function paintTitleBitmaps()
{
    drawString(titlectx, 'This is a demo of the JavaScript/HTML5 game loop',32,32);
    drawString(winctx, 'Your game should always have an ending',32,32);
}

function makeTitleBitmaps()
{
    titleBitmap = document.createElement('canvas');
    titleBitmap.width = SCREENWIDTH;
    titleBitmap.height = SCREENHEIGHT;
    titlectx = titleBitmap.getContext('2d');
    winBitmap = document.createElement('canvas');
    winBitmap.width = SCREENWIDTH;
    winBitmap.height = SCREENHEIGHT;
    winctx = winBitmap.getContext('2d');
    bitfont = new Image();
    bitfont.src = "graphics/bitfont.png";
    bitfont.onload = paintTitleBitmaps;
}

function resetGame()
{
    batx = 128;
    x = 320;
    y = 128;
    dx = -4;
    dy = 4;

    fragments = [
	[[500.000000,368.000000],[302.000000,414.000000],[235.000000,414.000000],[218.000000,388.000000],[243.000000,333.000000],[295.000000,333.000000]],
	[[243.000000,433.000000],[218.000000,488.000000],[164.000000,487.000000],[159.000000,456.000000],[176.000000,415.000000],[227.000000,414.000000]],
	[[204.000000,362.000000],[213.000000,384.000000],[205.000000,404.000000],[167.000000,403.000000],[201.000000,433.000000],[97.000000,368.000000],[145.000000,360.000000]]
    ];

}

function init()
{
    mode = MODE_TITLE;
    playerImage = getImage("player");
    springSound = new Audio("audio/boing.wav");
    makeTitleBitmaps();
    return true;
}

function TaggedPoint(coords, polygon, pointid)
{
    this.x1 = coords[0];
    this.y1 = coords[1];
    this.ident = polygon.ident+"-"+pointid;
    this.polygon = polygon
}

function TaggedLine(x1, y1, x2, y2, polygon, lineid){
    this.x1 = x1;
    this.x2 = x2;
    this.y1 = y1;
    this.y2 = y2;
    this.polygon = polygon;
    this.ident = polygon.ident+"-l"+lineid;
}

function TaggedPoly(ident, pointarray, region) {
    this.poly = pointarray;
    this.region = region;
    this.ident = ident;

}

function Collision(ix,iy,dist,outAngle,obj)
{
    this.ix = ix;
    this.iy = iy;
    this.dist = dist;
    this.outAngle = outAngle;
    this.obj = obj;
}
function getTaggedPolyLines(taggedPolygon)
{
    lines = new Array();
    poly = taggedPolygon.poly;
    for(p=0;p<poly.length-1;p++) {
        lines.push(new TaggedLine(poly[p][0],poly[p][1],poly[p+1][0],poly[p+1][1],taggedPolygon,p));
    }
    return lines;
}

function degrees(r) {
    return r*(180/Math.PI);
}

function lineIntersection(ballx, bally, ballxv, ballyv,
			  lstartx, lstarty, lxv, lyv)
{
    j = (lstarty/ballyv - bally/ballyv + ballx/ballxv -lstartx/ballxv)/(lxv/ballxv -lyv/ballyv);
    i = (lstartx+j*lxv-ballx)/ballxv;
    return [i,j];
}

function lineLen(x,y)
{
    return Math.sqrt(x*x+y*y);
}

function intersectPoly(poly, collisions, ball, considerRadius, lastCollisionObjectID)
{
    lines = getTaggedPolyLines(poly);
    hitline = null;
    lowi = 1.1;

    for(ln=0;ln<lines.length;ln++) {
	l = lines[ln]
        if(l.ident == lastCollisionObjectID) continue;
        lxv = l.x2-l.x1;
	lyv = l.y2-l.y1;
	lnormx = -lyv;
        lnormy = lxv;
        normScale = lineLen(lnormx,lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
        if(dotproduct>0) {
            lnormx = -lnormx;
            lnormy = -lnormy;
	}
        if(considerRadius) {
            // Make a parallel line
            lstartx = l.x1+lnormx*ball.radius;
            lstarty = l.y1+lnormy*ball.radius;
	}
        else {
            lstartx = l.x1;
            lstarty = l.y1;
	}

        res = lineIntersection(ball.x,ball.y,ball.dx,ball.dy,
                               lstartx,lstarty,lxv,lyv);
        i = res[0];
	j = res[1];
        if(i>=0 && i<=1 && j>=0 && j<=1) {
            if(i<lowi) {
                lowi = i;
                hitline = l;
	    }
	}

        }
    if(hitline != null) {
	// Ok, now we can figure out the angle wrt the surface normal...
        lxv = hitline.x2 - hitline.x1;
        lyv = hitline.y2 - hitline.y1;

        lnormx = -lyv;
        lnormy = lxv;
        normScale = Math.sqrt(lnormx*lnormx+lnormy*lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
	// This time, we make the normal in the same direction as the ball vector
        if(dotproduct<0) {
	    lnormx = -lnormx;
	    lnormy = -lnormy;
	}
        lenNormal = 1.0;
        ballToIntersectX = lowi*ball.dx;
        ballToIntersectY = lowi*ball.dy;
        distToIntersect = Math.sqrt(ballToIntersectX*ballToIntersectX
                                    +ballToIntersectY*ballToIntersectY);
        ballToIntersectX /= distToIntersect;
        ballToIntersectY /= distToIntersect;
        angle = Math.acos(ballToIntersectX*lnormx+ballToIntersectY*lnormy);
        console.log("Collision is at "+degrees(angle)+" degrees to surface normal");
        normAngle = Math.atan2(lnormy,lnormx)+Math.PI;
        incident = Math.atan2(ball.dy,ball.dx)+Math.PI;
        angle =  incident - normAngle;

        outangle = normAngle - angle;

        //if(poly.region.collide == False) {
        //    outangle = incident - Math.PI;
	//}
        collisions.push(new Collision(ball.x + lowi*ball.dx, ball.y+lowi*ball.dy, lowi, outangle,hitline));
	console.log("Intersection with polygon "+poly.ident+" line "+hitline.ident);
    }
    return collisions;
}

function checkClosestApproach(point, startx, starty, dx, dy)
{
    normx = -dy;
    normy = dx;
    res = lineIntersection(startx,starty,dx,dy,
                             point.x1,point.y1,normx,normy);
    i = res[0]; j = res[1];
    dist = Math.abs(j*lineLen(normx,normy));
    return [dist,i];
}
function intersectVertices(points, collisions, ballx,bally,ballxv,ballyv, ballRadius,lastCollisionObjectID)
{
    for(pointindex=0;pointindex<points.length;pointindex++) {
	p = points[pointindex];
        if(p.ident == lastCollisionObjectID) continue;
        res = checkClosestApproach(p,ballx,bally,ballxv,ballyv);

	d = res[0]; i = res[1];
        console.log("Closest approach to "+p.ident+" is "+d);
        if(d<ballRadius) {
            diff = ballRadius - d;
            dist = Math.sqrt(ballRadius*ballRadius-d*d);
            iPoint = (lineLen(ballxv,ballyv)*i-dist)/lineLen(ballxv,ballyv); // Distance to intersect
            if(iPoint >=0 && iPoint <=1) {
                ix = ballx+ballxv*iPoint;
                iy = bally+ballyv*iPoint;
                radiusAng = Math.atan2(iy-p.y1,ix-p.x1);
                incident = Math.atan2(ballyv,ballxv)+Math.PI;
                angle =  incident - radiusAng;
                console.log("Collides with ident "+p.ident+" Incident angle = "+degrees(incident));
                console.log("Surface normal angle = "+degrees(radiusAng));
                outangle = radiusAng - angle;
                //if(p.polygon.region.collide == False):
                //outangle = incident-Math.PI
                collisions.push(new Collision(ix,iy, iPoint, outangle, p))
	    }
	}
    }
}

function animate()
{
    if(x > SCREENWIDTH || x<0)  dx = -dx;
    if(y > SCREENHEIGHT || y<0)  dy = -dy;
    var ball = { 'x': x, 'y': y, 'dx': dx, 'dy': dy, 'radius': 8 };
    collisions = new Array();
    for(f=0;f<fragments.length;f++) {
	poly = new TaggedPoly("poly"+f, fragments[f], null);
	lastCollisionObjectID = "";
	collisions = intersectPoly(poly, collisions, ball, 1, lastCollisionObjectID);
    }

    points = new Array();
    for(f=0;f<fragments.length;f++) {
	poly = new TaggedPoly("poly"+f, fragments[f], null);
	for(p=0;p<poly.poly.length;p++) {
	    points.push(new TaggedPoint(poly.poly[p], poly, p));
	}
    }

    intersectVertices(points, collisions, ball.x,ball.y,ball.dx,ball.dy, ball.radius,lastCollisionObjectID)
    closestDist = 1.1;
    closest = null;

    for(c=0;c<collisions.length;c++) {
	col = collisions[c];
        console.log(col.ix, col.iy, col.dist, col.outAngle);
        if(col.dist < closestDist) {
            closest = col;
            closestDist = col.dist;
	}
    }
    if(closest != null) {
        console.log("Identified collision as one at dist "+closestDist);
	ctx.beginPath();
	ctx.moveTo(x,y);
	x = closest.ix;
        y = closest.iy;
        dist = lineLen(dx,dy);
        //distRemain = dist*(1.0-closest.dist);
        dx = dist*Math.cos(closest.outAngle);
        dy = dist*Math.sin(closest.outAngle);
	console.log("Moving to "+x+","+y+" with vel "+dx+","+dy);
//	stopRunloop=true;
	ctx.lineTo(x,y);
	ctx.stroke();

    } else {
	x += dx;
	y += dy;
    }

}

function draw() {
    ctx.fillStyle = "#0000ff";
    //ctx.fillRect(0, 0, SCREENWIDTH, SCREENHEIGHT);

    if(mode == MODE_TITLE) {
	ctx.drawImage(titleBitmap, 0, 0);
	return;
    }

    ctx.drawImage(playerImage, batx, 400);
    ctx.beginPath();
    ctx.arc(x, y, 8, 0, 2 * Math.PI, false);
    ctx.strokeStyle = 'white';
    ctx.stroke();

    ctx.fillStyle = 'red';

    for(f=0;f<fragments.length;f++) {
	poly = fragments[f];
	ctx.beginPath();
	ctx.moveTo(poly[0][0], poly[0][1]);
	for(p=1;p<poly.length;p++) {
	    point = poly[p];
	    ctx.lineTo(point[0], point[1]);
	    }
	ctx.closePath();
	ctx.stroke()
	}

    if(mode == MODE_WIN) {
	ctx.drawImage(winBitmap, 0, 0);
    }
}

function processKeys() {
    if(keysDown[37] || keysDown[65]) batx -= 8;
    if(keysDown[39] || keysDown[68]) batx += 8;
    if(batx < 0) batx = 0;
    if(batx > SCREENWIDTH - playerImage.width)  batx = SCREENWIDTH - playerImage.width;
}

function drawRepeat() {
    if(mode != MODE_TITLE) {
	processKeys();
	animate();
    }
    draw();
    if(!stopRunloop) setTimeout('drawRepeat()',20);
}

if (canvas.getContext('2d')) {
    stopRunloop = false;
    ctx = canvas.getContext('2d');
    ctx.fillStyle = "#0000ff";
    ctx.fillRect(0, 0, SCREENWIDTH, SCREENHEIGHT);
    body.onkeydown = function (event) {
	var c = event.keyCode;
        keysDown[c] = 1;
	if(c == 81) {
	    stopRunloop=true;
	}
	if(c == 32) {
	    if(mode == MODE_TITLE) {
		resetGame();
		mode = MODE_PLAY;
	    }
	}
	if(c == 82) {
	    if(mode == MODE_WIN) {
		mode = MODE_TITLE;
	    }
	}
    };

    body.onkeyup = function (event) {
	var c = event.keyCode;
        keysDown[c] = 0;
    };

    if(init()) {
      drawRepeat();
    }
}
<commit_msg>Correction to collision - last line in polygon<commit_after>var canvas = document.getElementsByTagName('canvas')[0];
var ctx = null;
var body = document.getElementsByTagName('body')[0];
var keysDown = new Array();
var SCREENWIDTH  = 640;
var SCREENHEIGHT = 480;
var MODE_TITLE = 0;
var MODE_PLAY  = 1;
var MODE_WIN   = 2;

function getImage(name)
{
    image = new Image();
    image.src = 'graphics/'+name+'.png';
    return image;
}

function drawChar(context, c, x, y)
{
    c = c.charCodeAt(0);
    if(c > 0) {
        context.drawImage(bitfont, c*6, 0, 6,8, x, y, 12, 16);
    }
}

function drawString(context, string, x, y) {
    string = string.toUpperCase();
    for(i = 0; i < string.length; i++) {
	drawChar(context, string[i], x, y);
	x += 12;
    }
}

function paintTitleBitmaps()
{
    drawString(titlectx, 'This is a demo of the JavaScript/HTML5 game loop',32,32);
    drawString(winctx, 'Your game should always have an ending',32,32);
}

function makeTitleBitmaps()
{
    titleBitmap = document.createElement('canvas');
    titleBitmap.width = SCREENWIDTH;
    titleBitmap.height = SCREENHEIGHT;
    titlectx = titleBitmap.getContext('2d');
    winBitmap = document.createElement('canvas');
    winBitmap.width = SCREENWIDTH;
    winBitmap.height = SCREENHEIGHT;
    winctx = winBitmap.getContext('2d');
    bitfont = new Image();
    bitfont.src = "graphics/bitfont.png";
    bitfont.onload = paintTitleBitmaps;
}

function resetGame()
{
    batx = 128;
    x = 320;
    y = 128;
    dx = -4;
    dy = 4;

    fragments = [
	[[500.000000,368.000000],[302.000000,414.000000],[235.000000,414.000000],[218.000000,388.000000],[243.000000,333.000000],[295.000000,333.000000]],
	[[243.000000,433.000000],[218.000000,488.000000],[164.000000,487.000000],[159.000000,456.000000],[176.000000,415.000000],[227.000000,414.000000]],
	[[204.000000,162.000000],[213.000000,184.000000],[205.000000,204.000000],[167.000000,203.000000],[201.000000,233.000000],[97.000000,168.000000],[145.000000,160.000000]]
    ];

}

function init()
{
    mode = MODE_TITLE;
    playerImage = getImage("skateboard");
    springSound = new Audio("audio/boing.wav");
    makeTitleBitmaps();
    return true;
}

function TaggedPoint(coords, polygon, pointid)
{
    this.x1 = coords[0];
    this.y1 = coords[1];
    this.ident = polygon.ident+"-"+pointid;
    this.polygon = polygon
}

function TaggedLine(x1, y1, x2, y2, polygon, lineid){
    this.x1 = x1;
    this.x2 = x2;
    this.y1 = y1;
    this.y2 = y2;
    this.polygon = polygon;
    this.ident = polygon.ident+"-l"+lineid;
}

function TaggedPoly(ident, pointarray, region) {
    this.poly = pointarray;
    this.region = region;
    this.ident = ident;

}

function Collision(ix,iy,dist,outAngle,obj)
{
    this.ix = ix;
    this.iy = iy;
    this.dist = dist;
    this.outAngle = outAngle;
    this.obj = obj;
}
function getTaggedPolyLines(taggedPolygon)
{
    lines = new Array();
    poly = taggedPolygon.poly;
    for(p=0;p<poly.length-1;p++) {
        lines.push(new TaggedLine(poly[p][0],poly[p][1],poly[p+1][0],poly[p+1][1],taggedPolygon,p));
    }
    lines.push(new TaggedLine(poly[0][0],poly[0][1],poly[poly.length-1][0],poly[poly.length-1][1],taggedPolygon,p));
    return lines;
}

function degrees(r) {
    return r*(180/Math.PI);
}

function lineIntersection(ballx, bally, ballxv, ballyv,
			  lstartx, lstarty, lxv, lyv)
{
    j = (lstarty/ballyv - bally/ballyv + ballx/ballxv -lstartx/ballxv)/(lxv/ballxv -lyv/ballyv);
    i = (lstartx+j*lxv-ballx)/ballxv;
    return [i,j];
}

function lineLen(x,y)
{
    return Math.sqrt(x*x+y*y);
}

function intersectPoly(poly, collisions, ball, considerRadius, lastCollisionObjectID)
{
    lines = getTaggedPolyLines(poly);
    hitline = null;
    lowi = 1.1;

    for(ln=0;ln<lines.length;ln++) {
	l = lines[ln]
        if(l.ident == lastCollisionObjectID) continue;
        lxv = l.x2-l.x1;
	lyv = l.y2-l.y1;
	lnormx = -lyv;
        lnormy = lxv;
        normScale = lineLen(lnormx,lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
        if(dotproduct>0) {
            lnormx = -lnormx;
            lnormy = -lnormy;
	}
        if(considerRadius) {
            // Make a parallel line
            lstartx = l.x1+lnormx*ball.radius;
            lstarty = l.y1+lnormy*ball.radius;
	}
        else {
            lstartx = l.x1;
            lstarty = l.y1;
	}

        res = lineIntersection(ball.x,ball.y,ball.dx,ball.dy,
                               lstartx,lstarty,lxv,lyv);
        i = res[0];
	j = res[1];
        if(i>=0 && i<=1 && j>=0 && j<=1) {
            if(i<lowi) {
                lowi = i;
                hitline = l;
	    }
	}

        }
    if(hitline != null) {
	// Ok, now we can figure out the angle wrt the surface normal...
        lxv = hitline.x2 - hitline.x1;
        lyv = hitline.y2 - hitline.y1;

        lnormx = -lyv;
        lnormy = lxv;
        normScale = Math.sqrt(lnormx*lnormx+lnormy*lnormy);
        lnormx = lnormx / normScale;
        lnormy = lnormy / normScale;
        dotproduct = lnormx*ball.dx+lnormy*ball.dy;
	// This time, we make the normal in the same direction as the ball vector
        if(dotproduct<0) {
	    lnormx = -lnormx;
	    lnormy = -lnormy;
	}
        lenNormal = 1.0;
        ballToIntersectX = lowi*ball.dx;
        ballToIntersectY = lowi*ball.dy;
        distToIntersect = Math.sqrt(ballToIntersectX*ballToIntersectX
                                    +ballToIntersectY*ballToIntersectY);
        ballToIntersectX /= distToIntersect;
        ballToIntersectY /= distToIntersect;
        angle = Math.acos(ballToIntersectX*lnormx+ballToIntersectY*lnormy);
        console.log("Collision is at "+degrees(angle)+" degrees to surface normal");
        normAngle = Math.atan2(lnormy,lnormx)+Math.PI;
        incident = Math.atan2(ball.dy,ball.dx)+Math.PI;
        angle =  incident - normAngle;

        outangle = normAngle - angle;

        //if(poly.region.collide == False) {
        //    outangle = incident - Math.PI;
	//}
        collisions.push(new Collision(ball.x + lowi*ball.dx, ball.y+lowi*ball.dy, lowi, outangle,hitline));
	console.log("Intersection with polygon "+poly.ident+" line "+hitline.ident);
    }
    return collisions;
}

function checkClosestApproach(point, startx, starty, dx, dy)
{
    normx = -dy;
    normy = dx;
    res = lineIntersection(startx,starty,dx,dy,
                             point.x1,point.y1,normx,normy);
    i = res[0]; j = res[1];
    dist = Math.abs(j*lineLen(normx,normy));
    return [dist,i];
}
function intersectVertices(points, collisions, ballx,bally,ballxv,ballyv, ballRadius,lastCollisionObjectID)
{
    for(pointindex=0;pointindex<points.length;pointindex++) {
	p = points[pointindex];
        if(p.ident == lastCollisionObjectID) continue;
        res = checkClosestApproach(p,ballx,bally,ballxv,ballyv);

	d = res[0]; i = res[1];
        if(d<ballRadius) {
            diff = ballRadius - d;
            dist = Math.sqrt(ballRadius*ballRadius-d*d);
            iPoint = (lineLen(ballxv,ballyv)*i-dist)/lineLen(ballxv,ballyv); // Distance to intersect
            if(iPoint >=0 && iPoint <=1) {
                ix = ballx+ballxv*iPoint;
                iy = bally+ballyv*iPoint;
                radiusAng = Math.atan2(iy-p.y1,ix-p.x1);
                incident = Math.atan2(ballyv,ballxv)+Math.PI;
                angle =  incident - radiusAng;
                console.log("Collides with ident "+p.ident+" Incident angle = "+degrees(incident));
                console.log("Surface normal angle = "+degrees(radiusAng));
                outangle = radiusAng - angle;
                //if(p.polygon.region.collide == False):
                //outangle = incident-Math.PI
                collisions.push(new Collision(ix,iy, iPoint, outangle, p))
	    }
	}
    }
}

function animate()
{
    if(x > SCREENWIDTH || x<0)  dx = -dx;
    if(y > SCREENHEIGHT || y<0)  dy = -dy;
    var ball = { 'x': x, 'y': y, 'dx': dx, 'dy': dy, 'radius': 16 };
    collisions = new Array();
    for(f=0;f<fragments.length;f++) {
	poly = new TaggedPoly("poly"+f, fragments[f], null);
	lastCollisionObjectID = "";
	collisions = intersectPoly(poly, collisions, ball, 1, lastCollisionObjectID);
    }

    points = new Array();
    for(f=0;f<fragments.length;f++) {
	poly = new TaggedPoly("poly"+f, fragments[f], null);
	for(p=0;p<poly.poly.length;p++) {
	    points.push(new TaggedPoint(poly.poly[p], poly, p));
	}
    }

    intersectVertices(points, collisions, ball.x,ball.y,ball.dx,ball.dy, ball.radius,lastCollisionObjectID)
    closestDist = 1.1;
    closest = null;

    for(c=0;c<collisions.length;c++) {
	col = collisions[c];
        console.log(col.ix, col.iy, col.dist, col.outAngle);
        if(col.dist < closestDist) {
            closest = col;
            closestDist = col.dist;
	}
    }
    if(closest != null) {
        console.log("Identified collision as one at dist "+closestDist);
	ctx.beginPath();
	ctx.moveTo(x,y);
	x = closest.ix;
        y = closest.iy;
        dist = lineLen(dx,dy);
        //distRemain = dist*(1.0-closest.dist);
        dx = dist*Math.cos(closest.outAngle);
        dy = dist*Math.sin(closest.outAngle);
	console.log("Moving to "+x+","+y+" with vel "+dx+","+dy);
	// TODO: At the moment we only do one collision per check - we could get into trouble this way...
	ctx.lineTo(x,y);
	ctx.stroke();

    } else {
	x += dx;
	y += dy;
    }

}

function draw() {
    ctx.fillStyle = "#0000ff";
    ctx.fillRect(0, 0, SCREENWIDTH, SCREENHEIGHT);

    if(mode == MODE_TITLE) {
	ctx.drawImage(titleBitmap, 0, 0);
	return;
    }

    ctx.drawImage(playerImage, batx, 450);
    ctx.beginPath();
    ctx.arc(x, y, 16, 0, 2 * Math.PI, false);
    ctx.strokeStyle = 'white';
    ctx.stroke();

    ctx.fillStyle = 'red';

    for(f=0;f<fragments.length;f++) {
	poly = fragments[f];
	ctx.beginPath();
	ctx.moveTo(poly[0][0], poly[0][1]);
	for(p=1;p<poly.length;p++) {
	    point = poly[p];
	    ctx.lineTo(point[0], point[1]);
	    }
	ctx.closePath();
	ctx.stroke()
	}

    if(mode == MODE_WIN) {
	ctx.drawImage(winBitmap, 0, 0);
    }
}

function processKeys() {
    if(keysDown[37] || keysDown[65]) batx -= 8;
    if(keysDown[39] || keysDown[68]) batx += 8;
    if(batx < 0) batx = 0;
    if(batx > SCREENWIDTH - playerImage.width)  batx = SCREENWIDTH - playerImage.width;
}

function drawRepeat() {
    if(mode != MODE_TITLE) {
	processKeys();
	animate();
    }
    draw();
    if(!stopRunloop) setTimeout('drawRepeat()',20);
}

if (canvas.getContext('2d')) {
    stopRunloop = false;
    ctx = canvas.getContext('2d');
    ctx.fillStyle = "#0000ff";
    ctx.fillRect(0, 0, SCREENWIDTH, SCREENHEIGHT);
    body.onkeydown = function (event) {
	var c = event.keyCode;
        keysDown[c] = 1;
	if(c == 81) {
	    stopRunloop=true;
	}
	if(c == 32) {
	    if(mode == MODE_TITLE) {
		resetGame();
		mode = MODE_PLAY;
	    }
	}
	if(c == 82) {
	    if(mode == MODE_WIN) {
		mode = MODE_TITLE;
	    }
	}
    };

    body.onkeyup = function (event) {
	var c = event.keyCode;
        keysDown[c] = 0;
    };

    if(init()) {
      drawRepeat();
    }
}
<|endoftext|>


<commit_before>/**
 * @file Ripple component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import CSSTransition from 'react-transition-group/CSSTransition';
import classNames from 'classnames';

class Ripple extends Component {

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {className, style, duration, ...restProps} = this.props,

            rippleClassName = classNames('ripple', {
                [className]: className
            });

        return (
            <CSSTransition {...restProps}
                           classNames="ripple"
                           timeout={{enter: duration, exit: duration * 2}}>

            </CSSTransition>
        );

    }
}

Ripple.propTypes = {

    className: PropTypes.string,
    style: PropTypes.object,

    duration: PropTypes.number

};

Ripple.defaultProps = {
    duration: 500
};

export default Ripple;<commit_msg>update Ripple<commit_after>/**
 * @file Ripple component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import CSSTransition from 'react-transition-group/CSSTransition';
import classNames from 'classnames';

class Ripple extends Component {

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {className, style, duration, ...restProps} = this.props;

        return (
            <CSSTransition {...restProps}
                           classNames="ripple"
                           timeout={{enter: duration, exit: duration * 2}}>

            </CSSTransition>
        );

    }
}

Ripple.propTypes = {

    className: PropTypes.string,
    style: PropTypes.object,

    duration: PropTypes.number

};

Ripple.defaultProps = {
    duration: 500
};

export default Ripple;
<|endoftext|>


<commit_before>/* jshint browser: true, devel: true, indent: 2, curly: true, eqeqeq: true, futurehostile: true, latedef: true, undef: true, unused: true */
/* global $, document, Site, imagesLoaded, Swiper, zenscroll */
var Shuffle = window.shuffle;

Site = {
  mobileThreshold: 601,
  animationSpeed: 400,
  init: function() {
    var _this = this;

    $(window).resize(function(){
      _this.onResize();
    });

    $(document).ready(function () {
      Site.Galleries.init();
    });

    _this.fixWidows();

    Site.Shuffle.init();
    Site.Media.init();
    Site.Lightbox.init();
    Site.Luminaries.init();
    Site.Expandables.init();
    Site.Drawers.init();
    Site.Share.init();
  },

  onResize: function() {
//     var _this = this;

  },

  fixWidows: function() {
    // utility class mainly for use on headines to avoid widows [single words on a new line]
    $('.js-fix-widows').each(function(){
      var string = $(this).html();
      string = string.replace(/ ([^ ]*)$/,' $1');
      $(this).html(string);
    });
  },
};

Site.Shuffle = {
  init: function() {
    var _this = this;

    _this.shuffleContainer = document.getElementById('shuffle-container');

    if (_this.shuffleContainer) {
      _this.bind();
    }
  },

  bind: function() {
    var _this = this;

    imagesLoaded(_this.shuffleContainer, function() {
      _this.initShuffle();
    });
  },

  initShuffle: function() {
    var _this = this;

    $('#shuffle-preloader').remove();
    $(_this.shuffleContainer).removeClass('hidden');

    _this.shuffleInstance = new Shuffle(_this.shuffleContainer, {
      itemSelector: '.shuffle-item',
      sizer: '.shuffle-item',
      throttleTime: 200,
      staggerAmount: 10,
      staggerAmountMax: 150,
    });

    _this.shuffleContainer.addEventListener('load', function() {
      _this.update();
    }, true);
  },

  update: function() {
    var _this = this;

    if (_this.shuffleInstance) {
      _this.shuffleInstance.update();
    }
  }
};

Site.Galleries = {
  init: function() {
    var _this = this;
    var $galleries = $('.swiper-container');

    if ($galleries.length) {
      $galleries.each(function(index, container) {
        if ($(this).hasClass('home-carousel')) {
          _this.initHomeCarousel(index, container);
        } else {
          _this.initGalleryInstance(index, container);
        }
      });
    }
  },

  initGalleryInstance: function(index, container) {
    var _this = this;

    _this['gallery-instance' + index] = new Swiper(container, {
      speed: 400,
      pagination: '.swiper-pagination',
      paginationType: 'fraction',
      loop: true,
      onTap: function(swiper) {
        swiper.slideNext();
      }
    });
  },

  initHomeCarousel: function(index, container) {
    var _this = this;
    var $slides = $(container).find('.swiper-slide');

    $slides.each(function(index, item) {
      var background = $(item).data('background');

      $(item).css({
        'background-image': 'url(' + background + ')'
      });

    });

    _this['gallery-instance' + index] = new Swiper(container, {
      speed: 1250,
      autoplay: 3000,
      pagination: '.swiper-pagination',
      paginationType: 'bullets',
      paginationClickable: true,
      loop: true,
      paginationBulletRender: function(swiper, index, className) {
        return '
' + (index + 1) + '
';
      },
    });
  }
};

Site.Luminaries = {
  init: function() {
    if ($('body').hasClass('post-type-archive-luminaries')) {
      this.Archive.init();
    } else if ($('body').hasClass('single-luminaries')) {

    }
  }
};

Site.Luminaries.Archive = {
  init: function() {
    this.bind();
  },

  bind: function() {
    var _this = this;

    $('#luminaries-sort-alphabetical').on('click', function() {
      _this.sort('alphabetical');
      $(this).hide();
      $('#luminaries-sort-order').show();
    });

    $('#luminaries-sort-order').on('click', function() {
      _this.sort('order');
      $(this).hide();
      $('#luminaries-sort-alphabetical').show();
    });
  },

  sort: function(type) {
    var $posts = $('#posts');

    // this function sorts posts by specified order. all the .-type-luminaries children of #posts are sorted by the javascript sort function. the code is based on SO examples
    $posts.find('.type-luminaries').sort(function(a, b) {
      if (type === 'order') {
        // this orders by numerical value with largest numbers first (to match the menu_order). the + is typecasting to enforce integer
        return +b.getAttribute('data-sort-order') - +a.getAttribute('data-sort-order');
      } else {
        // this sorts by alphabetical order abc...
        if (a.getAttribute('data-sort-alphabetical') === 'a'){
          return 0;
        }

        if ( b.getAttribute('data-sort-alphabetical') === 'a'){
          return 1;
        }

        return (a.getAttribute('data-sort-alphabetical') > b.getAttribute('data-sort-' + type) ? 1 : -1);
      }
    }).appendTo($posts);
  }
};

Site.Expandables = {
  init: function() {
    var _this = this;

    _this.bind();
  },

  bind: function() {
    $('.expandable-toggle').click( function() {

      $(this).toggle();

      var $expandableId = $(this).data('exapandable-id');

      // Toggle content
      $('#' + $expandableId).slideToggle(Site.animationSpeed, function() {
        if (Site.Shuffle.shuffleInstance) {
          Site.Shuffle.update();
        }
      });
    });
  },

};

Site.Drawers = {
  init: function() {
    var _this = this;

    _this.bind();

    _this.$validationError = $('.gform_validation_error');
    _this.$submitConfirmation = $('.gform_confirmation_message');

    if (_this.$validationError.length) {
      _this.openSubmitSection(_this.$validationError);
    }

    if (_this.$submitConfirmation.length) {
      _this.openSubmitSection(_this.$submitConfirmation);
    }

    if ($('body').hasClass('page-about')) {
      _this.About.init();
    }
  },

  bind: function() {
    $('.drawer-toggle').click( function() {
      var $drawer = $(this).data('drawer-id');

      // Toggle content
      $('#' + $drawer).slideToggle(Site.animationSpeed);
    });
  },

  openSubmitSection: function($target) {
    for (var x = 0; x < $target.length; x++) {
      $target.closest('.drawer-content').slideToggle(Site.animationSpeed, function() {
        $('html, body').animate({
          scrollTop: $(this).offset().top
        }, Site.animationSpeed);
      });
    }
  },
};

Site.Drawers.About = {
  init: function() {
    var _this = this;

    _this.bind();
  },

  bind: function() {
    var _this = this;

    $('.about-page-drawer-trigger').on('click', function() {
      _this.toggleDrawer(this);
    });
  },

  toggleDrawer: function(clickedTrigger) {
    var target = $(clickedTrigger).data('target');

    if ($(clickedTrigger).hasClass('active')) {
      return;
    }

    $('.about-page-drawer.active').slideUp((Site.animationSpeed + 150), function() {
      $('#about-drawer-' + target).slideDown(Site.animationSpeed);

      $('.about-page-drawer-trigger.active').removeClass('active');
      $(clickedTrigger).addClass('active');

      $('.about-page-drawer.active').removeClass('active');
      $('#about-drawer-' + target).addClass('active');
    });
  }
};

Site.Media = {
  init: function() {
    var _this = this;

    _this.bind();
  },

  bind: function() {
    var _this = this;

    if ($('.page-sound-and-vision').length) {
      $('.media-item-image-holder').on({
        'click': function() {
          var $target = $(this).parents('.media-item');

          _this.unloadActive();

          $target.addClass('active');

          _this.loadMedia($target);

          Site.Shuffle.update();

          clearTimeout(_this.scrollToTimeout);
          _this.scrollToTimeout = setTimeout(function() {
            zenscroll.to($target[0]);
          }, 200);
        }
      });
    }
  },

  unloadActive: function() {
    var _this = this;
    var $active = $('.media-item.active');

    if ($active.hasClass('playing-video')) {
      $('#media-item-video-embed').remove();
      $active.removeClass('playing-video');
    }

    if ($active.hasClass('playing-audio')) {
      $('#media-item-audio-embed').remove();
      $active.removeClass('playing-audio');
    }

    $active.removeClass('active');
  },

  loadMedia: function($item) {
    var _this = this;
    var data = $item.data();

    if (data.vimeo) {
      _this.loadVideo($item, data.vimeo);
    } else if (data.soundcloud) {
      _this.loadAudio($item, data.soundcloud);
    }
  },

  loadVideo: function($item, vimeoId) {
    var _this = this;
    var insert = '

';

    $item.addClass('playing-video');
    $item.find('.media-item-image-holder').append(insert);
  },

  loadAudio: function($item, soundcloudUrl) {
    var _this = this;
    var insert = '

';

    $item.addClass('playing-audio');
    $item.find('.media-item-image-holder').append(insert);
  },
};

Site.Lightbox = {
  init: function() {
    var _this = this;

    _this.$lightbox = $('#lightbox');
    _this.$lightboxTitle = $('#lightbox-title');
    _this.$lightboxContent = $('#lightbox-content');

    _this.bind();
  },

  bind: function() {
    var _this = this;

    $('[data-lightbox]').on({
      'click': function() {
        var data = $(this).data();

        _this.setTitle(data.lightboxTitle);

        if (data.lightbox === 'image') {
          _this.openImage(data);
        } else if (data.lightbox === 'gallery') {
          _this.openGallery(this);
        }

      }
    });

    _this.$lightbox.on({
      'click': function(e) {
        if (e.target.id === 'lightbox' || e.target.id === 'lightbox-close') {
          _this.hide();
        }
      }
    });
  },

  show: function() {
    var _this = this;

    $('body').addClass('lightbox-active');
    _this.$lightbox.css('display', 'flex');

  },

  showGallery: function(gallery) {
    var _this = this;

    _this.show();

    new Swiper(gallery, {
      speed: 400,
      pagination: '.swiper-pagination',
      paginationType: 'fraction',
      loop: true,
      onTap: function(swiper) {
        swiper.slideNext();
      }
    });
  },

  hide: function() {
    var _this = this;

    _this.$lightbox.hide();
    $('body').removeClass('lightbox-active');
    _this.$lightboxTitle.html('');
    _this.$lightboxContent.html('');
  },

  setTitle: function(title) {
    var _this = this;

    _this.$lightboxTitle.html(title);
  },

  openImage: function(data) {
    var _this = this;
    var insert = '';

    _this.$lightboxContent.append(insert);

    _this.show();
  },

  openGallery: function(element) {
    var _this = this;

    var $gallery = $(element).siblings('.gallery').clone();
    $gallery.addClass('swiper-container').removeClass('u-hidden');
    _this.$lightboxContent.append($gallery);

    _this.showGallery($gallery);
  },
};

Site.Share = {
  init: function() {
    var _this = this;

    if ($('.share-widget').length) {
      _this.bind();
    }
  },

  bind: function() {
    var _this = this;

    $('.share-trigger').on({
      'click': function() {
        var $target = $(this).parent();

        _this.toggle($target);
      }
    });
  },

  toggle: function($shareWidget) {
    var _this = this;

    if ($shareWidget.data('open')) {
      $shareWidget.find('.share-trigger').show();
      $shareWidget.find('.share-list').hide();
      $shareWidget.data('open', false);
    } else {
      $shareWidget.find('.share-trigger').hide();
      $shareWidget.find('.share-list').css('display', 'flex');

    }
  }
}

Site.init();
<commit_msg>fix private soundcloud embed on javascript<commit_after>/* jshint browser: true, devel: true, indent: 2, curly: true, eqeqeq: true, futurehostile: true, latedef: true, undef: true, unused: true */
/* global $, document, Site, imagesLoaded, Swiper, zenscroll */
var Shuffle = window.shuffle;

Site = {
  mobileThreshold: 601,
  animationSpeed: 400,
  init: function() {
    var _this = this;

    $(window).resize(function(){
      _this.onResize();
    });

    $(document).ready(function () {
      Site.Galleries.init();
    });

    _this.fixWidows();

    Site.Shuffle.init();
    Site.Media.init();
    Site.Lightbox.init();
    Site.Luminaries.init();
    Site.Expandables.init();
    Site.Drawers.init();
    Site.Share.init();
  },

  onResize: function() {
//     var _this = this;

  },

  fixWidows: function() {
    // utility class mainly for use on headines to avoid widows [single words on a new line]
    $('.js-fix-widows').each(function(){
      var string = $(this).html();
      string = string.replace(/ ([^ ]*)$/,' $1');
      $(this).html(string);
    });
  },
};

Site.Shuffle = {
  init: function() {
    var _this = this;

    _this.shuffleContainer = document.getElementById('shuffle-container');

    if (_this.shuffleContainer) {
      _this.bind();
    }
  },

  bind: function() {
    var _this = this;

    imagesLoaded(_this.shuffleContainer, function() {
      _this.initShuffle();
    });
  },

  initShuffle: function() {
    var _this = this;

    $('#shuffle-preloader').remove();
    $(_this.shuffleContainer).removeClass('hidden');

    _this.shuffleInstance = new Shuffle(_this.shuffleContainer, {
      itemSelector: '.shuffle-item',
      sizer: '.shuffle-item',
      throttleTime: 200,
      staggerAmount: 10,
      staggerAmountMax: 150,
    });

    _this.shuffleContainer.addEventListener('load', function() {
      _this.update();
    }, true);
  },

  update: function() {
    var _this = this;

    if (_this.shuffleInstance) {
      _this.shuffleInstance.update();
    }
  }
};

Site.Galleries = {
  init: function() {
    var _this = this;
    var $galleries = $('.swiper-container');

    if ($galleries.length) {
      $galleries.each(function(index, container) {
        if ($(this).hasClass('home-carousel')) {
          _this.initHomeCarousel(index, container);
        } else {
          _this.initGalleryInstance(index, container);
        }
      });
    }
  },

  initGalleryInstance: function(index, container) {
    var _this = this;

    _this['gallery-instance' + index] = new Swiper(container, {
      speed: 400,
      pagination: '.swiper-pagination',
      paginationType: 'fraction',
      loop: true,
      onTap: function(swiper) {
        swiper.slideNext();
      }
    });
  },

  initHomeCarousel: function(index, container) {
    var _this = this;
    var $slides = $(container).find('.swiper-slide');

    $slides.each(function(index, item) {
      var background = $(item).data('background');

      $(item).css({
        'background-image': 'url(' + background + ')'
      });

    });

    _this['gallery-instance' + index] = new Swiper(container, {
      speed: 1250,
      autoplay: 3000,
      pagination: '.swiper-pagination',
      paginationType: 'bullets',
      paginationClickable: true,
      loop: true,
      paginationBulletRender: function(swiper, index, className) {
        return '
' + (index + 1) + '
';
      },
    });
  }
};

Site.Luminaries = {
  init: function() {
    if ($('body').hasClass('post-type-archive-luminaries')) {
      this.Archive.init();
    } else if ($('body').hasClass('single-luminaries')) {

    }
  }
};

Site.Luminaries.Archive = {
  init: function() {
    this.bind();
  },

  bind: function() {
    var _this = this;

    $('#luminaries-sort-alphabetical').on('click', function() {
      _this.sort('alphabetical');
      $(this).hide();
      $('#luminaries-sort-order').show();
    });

    $('#luminaries-sort-order').on('click', function() {
      _this.sort('order');
      $(this).hide();
      $('#luminaries-sort-alphabetical').show();
    });
  },

  sort: function(type) {
    var $posts = $('#posts');

    // this function sorts posts by specified order. all the .-type-luminaries children of #posts are sorted by the javascript sort function. the code is based on SO examples
    $posts.find('.type-luminaries').sort(function(a, b) {
      if (type === 'order') {
        // this orders by numerical value with largest numbers first (to match the menu_order). the + is typecasting to enforce integer
        return +b.getAttribute('data-sort-order') - +a.getAttribute('data-sort-order');
      } else {
        // this sorts by alphabetical order abc...
        if (a.getAttribute('data-sort-alphabetical') === 'a'){
          return 0;
        }

        if ( b.getAttribute('data-sort-alphabetical') === 'a'){
          return 1;
        }

        return (a.getAttribute('data-sort-alphabetical') > b.getAttribute('data-sort-' + type) ? 1 : -1);
      }
    }).appendTo($posts);
  }
};

Site.Expandables = {
  init: function() {
    var _this = this;

    _this.bind();
  },

  bind: function() {
    $('.expandable-toggle').click( function() {

      $(this).toggle();

      var $expandableId = $(this).data('exapandable-id');

      // Toggle content
      $('#' + $expandableId).slideToggle(Site.animationSpeed, function() {
        if (Site.Shuffle.shuffleInstance) {
          Site.Shuffle.update();
        }
      });
    });
  },

};

Site.Drawers = {
  init: function() {
    var _this = this;

    _this.bind();

    _this.$validationError = $('.gform_validation_error');
    _this.$submitConfirmation = $('.gform_confirmation_message');

    if (_this.$validationError.length) {
      _this.openSubmitSection(_this.$validationError);
    }

    if (_this.$submitConfirmation.length) {
      _this.openSubmitSection(_this.$submitConfirmation);
    }

    if ($('body').hasClass('page-about')) {
      _this.About.init();
    }
  },

  bind: function() {
    $('.drawer-toggle').click( function() {
      var $drawer = $(this).data('drawer-id');

      // Toggle content
      $('#' + $drawer).slideToggle(Site.animationSpeed);
    });
  },

  openSubmitSection: function($target) {
    for (var x = 0; x < $target.length; x++) {
      $target.closest('.drawer-content').slideToggle(Site.animationSpeed, function() {
        $('html, body').animate({
          scrollTop: $(this).offset().top
        }, Site.animationSpeed);
      });
    }
  },
};

Site.Drawers.About = {
  init: function() {
    var _this = this;

    _this.bind();
  },

  bind: function() {
    var _this = this;

    $('.about-page-drawer-trigger').on('click', function() {
      _this.toggleDrawer(this);
    });
  },

  toggleDrawer: function(clickedTrigger) {
    var target = $(clickedTrigger).data('target');

    if ($(clickedTrigger).hasClass('active')) {
      return;
    }

    $('.about-page-drawer.active').slideUp((Site.animationSpeed + 150), function() {
      $('#about-drawer-' + target).slideDown(Site.animationSpeed);

      $('.about-page-drawer-trigger.active').removeClass('active');
      $(clickedTrigger).addClass('active');

      $('.about-page-drawer.active').removeClass('active');
      $('#about-drawer-' + target).addClass('active');
    });
  }
};

Site.Media = {
  init: function() {
    var _this = this;

    _this.bind();
  },

  bind: function() {
    var _this = this;

    if ($('.page-sound-and-vision').length) {
      $('.media-item-image-holder').on({
        'click': function() {
          var $target = $(this).parents('.media-item');

          _this.unloadActive();

          $target.addClass('active');

          _this.loadMedia($target);

          Site.Shuffle.update();

          clearTimeout(_this.scrollToTimeout);
          _this.scrollToTimeout = setTimeout(function() {
            zenscroll.to($target[0]);
          }, 200);
        }
      });
    }
  },

  unloadActive: function() {
    var _this = this;
    var $active = $('.media-item.active');

    if ($active.hasClass('playing-video')) {
      $('#media-item-video-embed').remove();
      $active.removeClass('playing-video');
    }

    if ($active.hasClass('playing-audio')) {
      $('#media-item-audio-embed').remove();
      $active.removeClass('playing-audio');
    }

    $active.removeClass('active');
  },

  loadMedia: function($item) {
    var _this = this;
    var data = $item.data();

    if (data.vimeo) {
      _this.loadVideo($item, data.vimeo);
    } else if (data.soundcloud) {
      _this.loadAudio($item, data.soundcloud);
    }
  },

  loadVideo: function($item, vimeoId) {
    var _this = this;
    var insert = '

';

    $item.addClass('playing-video');
    $item.find('.media-item-image-holder').append(insert);
  },

  loadAudio: function($item, soundcloudUrl) {
    var _this = this;

    soundcloudUrl = _this.makeURL(soundcloudUrl);

    var insert = '

';

    $item.addClass('playing-audio');
    $item.find('.media-item-image-holder').append(insert);
  },

  makeURL: function(url) {
    var urlParts = url.split('/');

    // Check if url comes with token
    if (urlParts.length === 6) {

      // Replace token in path as a query param
      url = url.replace(urlParts[5],'?secret_token=' + urlParts[5]);
    }

    return encodeURIComponent(url);
  },
};

Site.Lightbox = {
  init: function() {
    var _this = this;

    _this.$lightbox = $('#lightbox');
    _this.$lightboxTitle = $('#lightbox-title');
    _this.$lightboxContent = $('#lightbox-content');

    _this.bind();
  },

  bind: function() {
    var _this = this;

    $('[data-lightbox]').on({
      'click': function() {
        var data = $(this).data();

        _this.setTitle(data.lightboxTitle);

        if (data.lightbox === 'image') {
          _this.openImage(data);
        } else if (data.lightbox === 'gallery') {
          _this.openGallery(this);
        }

      }
    });

    _this.$lightbox.on({
      'click': function(e) {
        if (e.target.id === 'lightbox' || e.target.id === 'lightbox-close') {
          _this.hide();
        }
      }
    });
  },

  show: function() {
    var _this = this;

    $('body').addClass('lightbox-active');
    _this.$lightbox.css('display', 'flex');

  },

  showGallery: function(gallery) {
    var _this = this;

    _this.show();

    new Swiper(gallery, {
      speed: 400,
      pagination: '.swiper-pagination',
      paginationType: 'fraction',
      loop: true,
      onTap: function(swiper) {
        swiper.slideNext();
      }
    });
  },

  hide: function() {
    var _this = this;

    _this.$lightbox.hide();
    $('body').removeClass('lightbox-active');
    _this.$lightboxTitle.html('');
    _this.$lightboxContent.html('');
  },

  setTitle: function(title) {
    var _this = this;

    _this.$lightboxTitle.html(title);
  },

  openImage: function(data) {
    var _this = this;
    var insert = '';

    _this.$lightboxContent.append(insert);

    _this.show();
  },

  openGallery: function(element) {
    var _this = this;

    var $gallery = $(element).siblings('.gallery').clone();
    $gallery.addClass('swiper-container').removeClass('u-hidden');
    _this.$lightboxContent.append($gallery);

    _this.showGallery($gallery);
  },
};

Site.Share = {
  init: function() {
    var _this = this;

    if ($('.share-widget').length) {
      _this.bind();
    }
  },

  bind: function() {
    var _this = this;

    $('.share-trigger').on({
      'click': function() {
        var $target = $(this).parent();

        _this.toggle($target);
      }
    });
  },

  toggle: function($shareWidget) {
    var _this = this;

    if ($shareWidget.data('open')) {
      $shareWidget.find('.share-trigger').show();
      $shareWidget.find('.share-list').hide();
      $shareWidget.data('open', false);
    } else {
      $shareWidget.find('.share-trigger').hide();
      $shareWidget.find('.share-list').css('display', 'flex');

    }
  }
}

Site.init();
<|endoftext|>


<commit_before>/**
 * @file Color vendor
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import Valid from './Valid';

/**
 * Convert Hue percent value to RGB value
 * @param Hue percent value {number}
 * @returns RGB {number[R, G, B]}
 * @private
 */
function _getHueRGB(percent) {

    const data = [[255, 0, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255], [0, 0, 255], [255, 0, 255], [255, 0, 0]];

    if (Valid.isPerCent(percent)) {
        return data[Math.floor(percent * 6)];
    }

    return data[0];

}

/**
 * Convert Hue degree value to RGB value
 * @param Hue degree value {number}
 * @returns RGB {number[R, G, B]}
 */
function hue2rgb(hue) {

    if (!Valid.isDeg(hue)) {
        return;
    }

    const percent = hue / 360,
        int = Math.floor(percent * 6),
        offset = Math.round((percent * 6 - int) * 255),
        rgb = _getHueRGB(percent);

    rgb[(int * 2 + 1) % 3] += offset * (Valid.isOdd(int) ? -1 : 1);

    return rgb;

}

/**
 * Convert RGB value to HSB value
 * @param RGB value {number[R, G, B]}
 * @returns HSB value {number[H, S, B]}
 */
function rgb2hsb(rgb) {

    if (!Valid.isRGB(rgb)) {
        return;
    }

    const [r, g, b] = rgb,
        max = Math.max(...rgb),
        min = Math.min(...rgb);

    let h = 0;

    if (max === r && g >= b) {
        h = (g - b) * 60 / (max - min) + 0;
    } else if (max === r && g < b) {
        h = (g - b) * 60 / (max - min) + 360;
    } else if (max === g) {
        h = (b - r) * 60 / (max - min) + 120;
    } else if (max === b) {
        h = (r - g) * 60 / (max - min) + 240;
    }

    return [h, max === 0 ? 0 : (max - min) / max, max / 255];

}

/**
 * Convert HSB value to RGB value
 * @param HSB value {number[H, S, B]}
 * @returns RGB value {number[R, G, B]}
 */
function hsb2rgb(hsb) {

    if (!Valid.isHSB(hsb)) {
        return;
    }

    let [h, s, v] = hsb;
    h = h === 360 ? 0 : h;

    const i = Math.floor(h / 60) % 6,
        f = h / 60 - i,
        p = v * (1 - s),
        q = v * (1 - f * s),
        t = v * (1 - (1 - f) * s);

    let r = 0, g = 0, b = 0;

    switch (i) {
        case 0:
            r = v;
            g = t;
            b = p;
            break;
        case 1:
            r = q;
            g = v;
            b = p;
            break;
        case 2:
            r = p;
            g = v;
            b = t;
            break;
        case 3:
            r = p;
            g = q;
            b = v;
            break;
        case 4:
            r = t;
            g = p;
            b = v;
            break;
        case 5:
            r = v;
            g = p;
            b = q;
            break;
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];

}

/**
 * Convert RGB value to hex format
 * @param RGB value {number[R, G, B]}
 * @returns RGB hex value {string}
 */
function rgb2hex(rgb) {

    if (!rgb || !Valid.isRGB(rgb)) {
        return;
    }

    return rgb.map(item => item.toString(16)).join('');

}

/**
 * Convert RGB hex value to RGB format
 * @param RGB hex value {string}
 * @param hasHash {bool}
 * @returns RGB value {number[R, G, B]}
 */
function hex2rgb(hex, hasHash) {

    if (!Valid.isHex(hex, hasHash)) {
        return;
    }

    function fn(i) {
        const j = hasHash ? 1 : 0;
        return parseInt(hex.slice(i + j, i + j + 2), 16);
    }

    return [fn(0), fn(2), fn(4)];

}

export default {
    hue2rgb,
    rgb2hsb,
    hsb2rgb,
    rgb2hex,
    hex2rgb
};
<commit_msg>update Color<commit_after>/**
 * @file Color vendor
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import Valid from './Valid';

/**
 * Convert Hue percent value to RGB value
 * @param Hue percent value {number}
 * @returns RGB {number[R, G, B]}
 * @private
 */
function _getHueRGB(percent) {

    const data = [[255, 0, 0], [255, 255, 0], [0, 255, 0], [0, 255, 255], [0, 0, 255], [255, 0, 255], [255, 0, 0]];

    if (Valid.isPerCent(percent)) {
        return data[Math.floor(percent * 6)];
    }

    return data[0];

}

/**
 * Convert Hue degree value to RGB value
 * @param Hue degree value {number}
 * @returns RGB {number[R, G, B]}
 */
function hue2rgb(hue) {

    if (!Valid.isDeg(hue)) {
        return;
    }

    const percent = hue / 360,
        int = Math.floor(percent * 6),
        offset = Math.round((percent * 6 - int) * 255),
        rgb = _getHueRGB(percent);

    rgb[(int * 2 + 1) % 3] += offset * (Valid.isOdd(int) ? -1 : 1);

    return rgb;

}

/**
 * Convert RGB value to HSB value
 * @param RGB value {number[R, G, B]}
 * @returns HSB value {number[H, S, B]}
 */
function rgb2hsb(rgb) {

    if (!Valid.isRGB(rgb)) {
        return;
    }

    const [r, g, b] = rgb,
        max = Math.max(...rgb),
        min = Math.min(...rgb);

    let h = 0;

    if (max === r && g >= b) {
        h = (g - b) * 60 / (max - min) + 0;
    } else if (max === r && g < b) {
        h = (g - b) * 60 / (max - min) + 360;
    } else if (max === g) {
        h = (b - r) * 60 / (max - min) + 120;
    } else if (max === b) {
        h = (r - g) * 60 / (max - min) + 240;
    }

    return [h ? Math.round(h) : h, max === 0 ? 0 : (max - min) / max, max / 255];

}

/**
 * Convert HSB value to RGB value
 * @param HSB value {number[H, S, B]}
 * @returns RGB value {number[R, G, B]}
 */
function hsb2rgb(hsb) {

    if (!Valid.isHSB(hsb)) {
        return;
    }

    let [h, s, v] = hsb;
    h = h === 360 ? 0 : h;

    const i = Math.floor(h / 60) % 6,
        f = h / 60 - i,
        p = v * (1 - s),
        q = v * (1 - f * s),
        t = v * (1 - (1 - f) * s);

    let r = 0, g = 0, b = 0;

    switch (i) {
        case 0:
            r = v;
            g = t;
            b = p;
            break;
        case 1:
            r = q;
            g = v;
            b = p;
            break;
        case 2:
            r = p;
            g = v;
            b = t;
            break;
        case 3:
            r = p;
            g = q;
            b = v;
            break;
        case 4:
            r = t;
            g = p;
            b = v;
            break;
        case 5:
            r = v;
            g = p;
            b = q;
            break;
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];

}

/**
 * Convert RGB value to hex format
 * @param RGB value {number[R, G, B]}
 * @returns RGB hex value {string}
 */
function rgb2hex(rgb) {

    if (!rgb || !Valid.isRGB(rgb)) {
        return;
    }

    return rgb.map(item => item.toString(16)).join('');

}

/**
 * Convert RGB hex value to RGB format
 * @param RGB hex value {string}
 * @param hasHash {bool}
 * @returns RGB value {number[R, G, B]}
 */
function hex2rgb(hex, hasHash) {

    if (!Valid.isHex(hex, hasHash)) {
        return;
    }

    function fn(i) {
        const j = hasHash ? 1 : 0;
        return parseInt(hex.slice(i + j, i + j + 2), 16);
    }

    return [fn(0), fn(2), fn(4)];

}

export default {
    hue2rgb,
    rgb2hsb,
    hsb2rgb,
    rgb2hex,
    hex2rgb
};
<|endoftext|>


<commit_before>import { baseUrl, localUrl } from '../api/config';
// import { checkResponseStatus } from '../api/helpers';

import { updatePieces } from './pieces';

import RNFS from 'react-native-fs';

import {
  TOGGLE_LOADING,
  // UPDATE_PROGRESS,
  CHANGE_TAB,
} from '../constants/actionTypes';

import {
  PIECES_TAB,
} from '../constants/tabs';

import { img2 } from '../mocks/image';

export const toggleLoading = () => ({
  type: TOGGLE_LOADING,
});

// export const updateProgress = (value) => ({
//   type: UPDATE_PROGRESS,
//   value,
// });

export const changeTab = index => ({
  type: CHANGE_TAB,
  index,
});

const requestPieces = (image, dispatch) => {

  return fetch(`${baseUrl}/submit`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Access-Key': 'myAccessKey',  // use in future for safer requests
    },
    body: JSON.stringify({
      // image: img2, // uncomment this line to test with img2 (useful to test with simulator)
      image,

      // TODO: Send file as FormData
      // image: {
      //   uri: imagePath,
      //   type: 'image/jpeg',
      //   name: 'photo.jpg',
      // },

      device_id: 'send_device_id', // use in future
    })
  })
    // .then(checkResponseStatus)
    .then(response => response.json())
    .then(data => dispatch(updatePieces(data.pieces)))
    .then(() => dispatch(toggleLoading()))
    .then(() => dispatch(changeTab(PIECES_TAB)))
    .catch(err => console.log('error in requestPieces', err));

};

const retrieveImageFromStorage = imagePath => {

  // Throws file does not exist
  // TODO: Fix file-retrieval for Android by sending FormData in requestPieces

  return RNFS.readFile(imagePath, 'base64')
    .then(image => {
      console.log('image', image);
      // return image;
      return image;
    })
    .catch(err => console.error(err));
};

export const processImage = (imagePath) => {

  return dispatch => {
    dispatch(toggleLoading());
    return Promise.resolve()
      .then(() => retrieveImageFromStorage(imagePath))
      // dispatch API call action here
      .then(image => requestPieces(image, dispatch))
      .catch(err => console.log('error in processImage: ', err));
  };

};
<commit_msg>Get global photo action into shape for testing<commit_after>import { baseUrl, localUrl } from '../api/config';
// import { checkResponseStatus } from '../api/helpers';

import { updatePieces } from './pieces';

import RNFS from 'react-native-fs';

import {
  TOGGLE_LOADING,
  // UPDATE_PROGRESS,
  CHANGE_TAB,
} from '../constants/actionTypes';

import {
  PIECES_TAB,
} from '../constants/tabs';

import { img2 } from '../mocks/image';
import { oneRed } from '../mocks/one-red';

export const toggleLoading = () => ({
  type: TOGGLE_LOADING,
});

// export const updateProgress = (value) => ({
//   type: UPDATE_PROGRESS,
//   value,
// });

export const changeTab = index => ({
  type: CHANGE_TAB,
  index,
});

const requestPieces = (image, dispatch) => {

  return fetch(`${baseUrl}/submit`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Access-Key': 'myAccessKey',  // use in future for safer requests
    },
    body: JSON.stringify({
      // image: img2, // uncomment this line to test with img2 (useful to test with simulator)
      image: oneRed, // uncomment this line to test with img2 (useful to test with simulator)
      // image,

      // TODO: Send file as FormData
      // image: {
      //   uri: imagePath,
      //   type: 'image/jpeg',
      //   name: 'photo.jpg',
      // },

      device_id: 'send_device_id', // use in future
    })
  })
    // .then(checkResponseStatus)
    .then(response => response.json())
    // TODO: Debug JSON response
    .then(data => dispatch(updatePieces(data.pieces)))
    .then(() => dispatch(toggleLoading()))
    .then(() => dispatch(changeTab(PIECES_TAB)))
    .catch(err => console.log('error in requestPieces', err));

};

const retrieveImageFromStorage = imagePath => {

  // Throws file does not exist
  // TODO: Fix file-retrieval for Android by sending FormData in requestPieces

  return RNFS.readFile(imagePath, 'base64')
    .then(image => {
      console.log('image', image);
      // return image;
      return image;
    })
    .catch(err => console.error(err));
};

export const processImage = (imagePath) => {

  return dispatch => {
    dispatch(toggleLoading());
    return Promise.resolve()
      .then(() => retrieveImageFromStorage(imagePath))
      // dispatch API call action here
      .then(image => requestPieces(image, dispatch))
      .catch(err => console.log('error in processImage: ', err));
  };

};
<|endoftext|>