<commit_before>// Extend the Enketo Core Form class, and expose it for local testing.
import { Form, FormModel } from 'enketo-core';
import events from 'enketo-core/src/js/event';
import config from 'enketo/config';
import gui from './gui';
import settings from './settings';
import './calculate';
import './relevant';
import './required';
import './page';
import './repeat';
import './input';
import './download-utils';

Form.constraintNames = Array.from(Array(21)).map(
    (val, i) => `constraint${i !== 0 ? i : ''}`
);

Object.defineProperty(Form, 'constraintClassesInvalid', {
    get: () => Form.constraintNames.map((n) => `invalid-${n}`),
});

Object.defineProperty(Form, 'constraintAttributes', {
    get: () =>
        Form.constraintNames.map((n, i) => `data-${i === 0 ? n : `oc-${n}`}`),
});

/**
 * This function doesn't actually evaluate constraints. It triggers
 * an event on nodes that have constraint dependency on the changed node(s).
 * This event is used in the discrepancy notes widget.
 *
 * @param  {[type]} updated - [description]
 */
const constraintUpdate = function (updated) {
    updated = updated || {};
    // If the update object is a repeat node (cloned=true), do nothing
    if (!updated.cloned) {
        this.getRelatedNodes('data-constraint', '', updated)
            // The filter below is commented out, because at the moment this.getRelatedNodes already takes
            // care of this (in enketo-core). However, it is not unrealistic to expect that in the future we will
            // not be able to rely on that as it may be considered a performance hack too far. In that case, uncomment below.
            //
            // Filter out the nodes that are inside a repeat instance other than
            // the repeat instance that contains the node that triggered the dataupdate
            // https://github.com/kobotoolbox/enketo-express/issues/741
            /* .filter( function() {
                var $input;
                var $repeat;
                var repeatIndex;
                if ( !updated.repeatPath ) {
                    return true;
                }
                $input = $( this );
                $repeat = $input.closest( '.or-repeat[name="' + updated.repeatPath + '"]' );
                if ( !$repeat.length ) {
                    return true;
                }
                repeatIndex = $( '.or-repeat[name="' + updated.repeatPath + '"]' ).index( $repeat );
                return repeatIndex === updated.repeatIndex;
            } ) */
            .trigger('constraintevaluated.oc', updated);
    }
};

/**
 * OC does not empty irrelevant nodes. Instead non-empty irrelevant nodes get an error until the user clears the value.
 * This function takes care of re-evaluating the branch when the value is cleared.
 *
 * @param  {[type]} updated - [description]
 * @return {[type]}         [description]
 */
const relevantErrorUpdate = function (updated) {
    const nodes = this.getRelatedNodes('data-relevant', '', updated)
        .get()
        .concat(this.getRelatedNodes('name', '[data-relevant]', updated).get())
        .concat(
            this.getRelatedNodes('data-name', '[data-relevant]', updated).get()
        )
        .filter((control) => !!control.closest('.invalid-relevant'))
        .map((n) =>
            n.matches('[data-relevant]')
                ? n
                : n.querySelector('[data-relevant]')
        );

    this.relevant.updateNodes(nodes);
};

const originalInit = Form.prototype.init;
const originalValidateInput = Form.prototype.validateInput;

Form.prototype.evaluationCascadeAdditions = [
    constraintUpdate,
    relevantErrorUpdate,
];

Form.prototype.init = function () {
    const that = this;
    let initialized = false;

    // Before any other change handlers, add the "strict check" handlers
    if (settings.strictViolationSelector) {
        this.view.$.on(
            'change.file',
            'input:not(.ignore)[data-required][data-oc-required-type="strict"], select:not(.ignore)[data-required][data-oc-required-type="strict"], textarea:not(.ignore)[data-required][data-oc-required-type="strict"]',
            function (evt) {
                if (initialized) {
                    that.strictRequiredCheckHandler(evt, this);
                }
            }
        ).on(
            'change.file',
            'input:not(.ignore)[data-constraint][data-oc-constraint-type="strict"], select:not(.ignore)[data-constraint][data-oc-constraint-type="strict"], textarea:not(.ignore)[data-constraint][data-oc-constraint-type="strict"]',
            function (evt) {
                if (initialized) {
                    that.strictConstraintCheckHandler(evt, this);
                }
            }
        );
    }

    const loadErrors = originalInit.call(this);

    initialized = true;

    return loadErrors;
};

Form.prototype.specialOcLoadValidate = function (includeRequired) {
    const that = this;
    let $collectionToValidate = this.getRelatedNodes('data-constraint');

    if (includeRequired) {
        $collectionToValidate = $collectionToValidate.add(
            this.getRelatedNodes('data-required')
        );
    }

    // Note, even if includeRequired is falsy, any empty question that has both a required and constraint expression
    // will show a required error.
    // So the above collection determining is just to limit the amount of validation the engine has to perform but it
    // still needs cleaning, because the engine will validate **all** expressions on the selected question.

    $collectionToValidate.each(function () {
        const control = this;
        that.validateInput(control).then((result) => {
            if (!result.requiredValid && !includeRequired) {
                // Undo the displaying of a required error message upon load.
                // Note: a failed required means there cannot be a failed constraint, because they are mutually exclusive
                // in the engine (constraint is only evaluated if question has a value).
                that.setValid(control, 'required');
            }
        });
    });
};

// No-op because OC clears non-relevant values immediately. Save a huge amount of time upon 'Close'.
Form.prototype.clearNonRelevant = () => {};

/**
 * Skip constraint (and required) validation if question is currently marked with "invalid-relevant" error.
 *
 * @param {[type]} $input - [description]
 * @param control
 * @return {[type]}        [description]
 */
Form.prototype.validateInput = function (control) {
    // There is a condition where a value change results in both an invalid-relevant and invalid-constraint,
    // where the invalid constraint is added *after* the invalid-relevant. I can reproduce in automated test (not manually).
    // It is probably related due to the asynchronicity of the constraint evaluation.
    //
    // To crudely resolve this, we remove any constraint error here.
    // However we do want some of the other things that validateInput does (ie. updating the required "*" visibility), so
    // we will still run it but then remove any invalid classes.
    //
    // This is very unfortunate, but these are the kind of acrobatics that are necessary to "fight" the built-in behavior of Enketo's form engine.
    return originalValidateInput.call(this, control).then((result) => {
        if (
            result &&
            (!result.requiredValid ||
                !result.constraintValid || // form.validateInput still returns true for hidden and disabled fields
                (Array.isArray(result.constraintValid) &&
                    result.constraintValid.some((valid) => valid === false)))
        ) {
            const question = control.closest('.question');
            if (question && question.classList.contains('invalid-relevant')) {
                // TODO: another perhaps more efficient approach would be to check with invalid-constraintN classes are currently present
                Form.constraintNames.forEach((constraint) =>
                    this.setValid(control, constraint)
                );
            }
        }

        return result;
    });
};

Form.prototype.strictRequiredCheckHandler = function (evt, input) {
    const that = this;
    const n = {
        path: this.input.getName(input),
        required: this.input.getRequired(input),
        val: this.input.getVal(input),
    };

    // No need to validate.
    if (
        n.readonly ||
        n.inputType === 'hidden' ||
        input.closest('.invalid-relevant')
    ) {
        return;
    }

    // Only now, will we determine the index (expensive).
    n.ind = this.input.getIndex(input);

    // Check required
    if (n.val === '' && this.model.node(n.path, n.ind).isRequired(n.required)) {
        const question = input.closest('.question');
        const msg = question.querySelector('.or-required-msg.active').innerHTML;
        gui.alertStrictError(msg);
        // Cancel propagation input
        evt.stopImmediatePropagation();
        const currentModelValue = that.model.node(n.path, n.ind).getVal();
        that.input.setVal(input, currentModelValue);
        // When changing this make sure that the radio picker's change
        // listener picks this event up.
        // https://github.com/OpenClinica/enketo-express-oc/issues/168
        input.dispatchEvent(events.InputUpdate());
        question.scrollIntoView();
    }
};

Form.prototype.strictConstraintCheckHandler = function (evt, input) {
    const that = this;
    const n = {
        path: this.input.getName(input),
        xmlType: this.input.getXmlType(input),
        constraint: this.input.getConstraint(input)[0],
        val: this.input.getVal(input),
    };

    // No need to validate.
    if (
        n.readonly ||
        n.inputType === 'hidden' ||
        !n.constraint ||
        input.closest('.invalid-relevant')
    ) {
        return;
    }

    // Only now, will we determine the index (expensive).
    n.ind = this.input.getIndex(input);

    // In order to evaluate the constraint, its value has to be set in the model.
    // This would trigger a fieldsubmission, which is what we're trying to prevent.
    // A heavy-handed dumb-but-safe approach is to clone the model and set the value there.
    const modelClone = new FormModel(
        new XMLSerializer().serializeToString(this.model.xml)
    );
    // TODO: initialize clone with **external data**.
    modelClone.init();
    // Set the value in the clone
    const updated = modelClone.node(n.path, n.ind).setVal(n.val, n.xmlType);
    // Check if strict constraint passes
    if (!updated) {
        return;
    }
    // Note: we don't use Enketo Core's nodeset.validateConstraintAndType here because it's asynchronous,
    // which means we couldn't selectively stop event propagation.
    const modelCloneNodeValue = modelClone.node(n.path, n.ind).getVal();

    if (modelCloneNodeValue.toString() === '') {
        return;
    }

    if (
        typeof n.constraint !== 'undefined' &&
        n.constraint !== null &&
        n.constraint.length > 0 &&
        !modelClone.evaluate(n.constraint, 'boolean', n.path, n.ind)
    ) {
        const question = input.closest('.question');
        const msg = question.querySelector(
            '.or-constraint-msg.active'
        ).innerHTML;
        gui.alertStrictError(msg);
        // Cancel propagation input
        evt.stopImmediatePropagation();
        const currentModelValue = that.model.node(n.path, n.ind).getVal();
        that.input.setVal(input, currentModelValue);
        // When changing this make sure that the radio picker's change
        // listener picks this event up.
        // https://github.com/OpenClinica/enketo-express-oc/issues/168
        input.dispatchEvent(events.InputUpdate());
        question.scrollIntoView();
    }
};

/**
 * Removes an invalid mark on a question in the form UI.
 * OC: customized to also work on groups
 *
 * @param {Element} control - form control HTML element
 * @param {string} [type] - One of "constraint", "required" and "relevant".
 */
Form.prototype.setValid = function (control, type) {
    const wrap = control.closest(
        '.question, .calculation, .or-group, .or-group-data'
    );

    if (!wrap) {
        // TODO: this condition occurs, at least in tests for itemsets, but we need find out how.
        return;
    }

    const classes = type
        ? [`invalid-${type}`]
        : [...wrap.classList].filter((cl) => cl.indexOf('invalid-') === 0);
    wrap.classList.remove(...classes);
};

/**
 * Marks a question as invalid in the form UI.
 * OC: customized to also work on groups
 *
 * @param {Element} control - form control HTML element
 * @param {string} [type] - One of "constraint", "required" and "relevant".
 */
Form.prototype.setInvalid = function (control, type = 'constraint') {
    const wrap = control.closest(
        '.question, .calculation, .or-group, .or-group-data'
    );

    if (!wrap) {
        // TODO: this condition occurs, at least in tests for itemsets, but we need find out how.
        return;
    }

    if (config.validatePage === false && this.isValid(control)) {
        this.blockPageNavigation();
    }

    wrap.classList.add(`invalid-${type}`);
};

// TODO can this function be removed entirely?
/**
 * Checks whether the question is not currently marked as invalid. If no argument is provided, it checks the whole form.
 * OC customization: added group
 *
 * @param {Element} node - form control HTML element
 * @return {!boolean} Whether the question/form is not marked as invalid.
 */
Form.prototype.isValid = function (node) {
    const invalidSelectors = ['.invalid-required', '.invalid-relevant'].concat(
        this.constraintClassesInvalid
    );
    if (node) {
        const questionOrGroup = node.closest(
            '.question, .calculation, .or-group, .or-group-data'
        );
        const cls = questionOrGroup.classList;

        return !invalidSelectors.some((selector) => cls.contains(selector));
    }

    return !this.view.html.querySelector(invalidSelectors.join(', '));
};

/**
 *
 * @param {*} control - form control HTML element
 * @param {*} result - result object obtained from Nodeset.validate
 */
Form.prototype.updateValidityInUi = function (control, result) {
    const passed = result.requiredValid !== false;

    if (result.constraintValid === null) {
        result.constraintValid = Array.from(Array(21)).map(() => null);
    }

    // Update UI
    if (result.requiredValid === false) {
        if (Array.isArray(result.constraintValid)) {
            result.constraintValid.forEach((valid, index) =>
                this.setValid(
                    control,
                    index === 0 ? 'constraint' : `constraint${index}`
                )
            );
        }
        this.setInvalid(control, 'required');
    } else {
        this.setValid(control, 'required');
        // if required === false, result.constraintValid returned by nodeset.validate is null
        if (Array.isArray(result.constraintValid)) {
            result.constraintValid.forEach((valid, index) => {
                const cls = index === 0 ? 'constraint' : `constraint${index}`;
                if (valid === true) {
                    this.setValid(control, cls);
                } else if (valid === false) {
                    passed === false;
                    this.setInvalid(control, cls);
                }
                // no need to do anything if valid === undefined
            });
        }
    }

    if (!passed) {
        control.dispatchEvent(events.Invalidated());
    }
};

/* eslint import/prefer-default-export: "off" */
export { Form };
<commit_msg>fixed: OC: if invalid field becomes valid, error state remains shown, #541<commit_after>// Extend the Enketo Core Form class, and expose it for local testing.
import { Form, FormModel } from 'enketo-core';
import events from 'enketo-core/src/js/event';
import config from 'enketo/config';
import gui from './gui';
import settings from './settings';
import './calculate';
import './relevant';
import './required';
import './page';
import './repeat';
import './input';
import './download-utils';

Form.constraintNames = Array.from(Array(21)).map(
    (val, i) => `constraint${i !== 0 ? i : ''}`
);

Object.defineProperty(Form, 'constraintClassesInvalid', {
    get: () => Form.constraintNames.map((n) => `invalid-${n}`),
});

Object.defineProperty(Form, 'constraintAttributes', {
    get: () =>
        Form.constraintNames.map((n, i) => `data-${i === 0 ? n : `oc-${n}`}`),
});

/**
 * This function doesn't actually evaluate constraints. It triggers
 * an event on nodes that have constraint dependency on the changed node(s).
 * This event is used in the discrepancy notes widget.
 *
 * @param  {[type]} updated - [description]
 */
const constraintUpdate = function (updated) {
    updated = updated || {};
    // If the update object is a repeat node (cloned=true), do nothing
    if (!updated.cloned) {
        this.getRelatedNodes('data-constraint', '', updated)
            // The filter below is commented out, because at the moment this.getRelatedNodes already takes
            // care of this (in enketo-core). However, it is not unrealistic to expect that in the future we will
            // not be able to rely on that as it may be considered a performance hack too far. In that case, uncomment below.
            //
            // Filter out the nodes that are inside a repeat instance other than
            // the repeat instance that contains the node that triggered the dataupdate
            // https://github.com/kobotoolbox/enketo-express/issues/741
            /* .filter( function() {
                var $input;
                var $repeat;
                var repeatIndex;
                if ( !updated.repeatPath ) {
                    return true;
                }
                $input = $( this );
                $repeat = $input.closest( '.or-repeat[name="' + updated.repeatPath + '"]' );
                if ( !$repeat.length ) {
                    return true;
                }
                repeatIndex = $( '.or-repeat[name="' + updated.repeatPath + '"]' ).index( $repeat );
                return repeatIndex === updated.repeatIndex;
            } ) */
            .trigger('constraintevaluated.oc', updated);
    }
};

/**
 * OC does not empty irrelevant nodes. Instead non-empty irrelevant nodes get an error until the user clears the value.
 * This function takes care of re-evaluating the branch when the value is cleared.
 *
 * @param  {[type]} updated - [description]
 * @return {[type]}         [description]
 */
const relevantErrorUpdate = function (updated) {
    const nodes = this.getRelatedNodes('data-relevant', '', updated)
        .get()
        .concat(this.getRelatedNodes('name', '[data-relevant]', updated).get())
        .concat(
            this.getRelatedNodes('data-name', '[data-relevant]', updated).get()
        )
        .filter((control) => !!control.closest('.invalid-relevant'))
        .map((n) =>
            n.matches('[data-relevant]')
                ? n
                : n.querySelector('[data-relevant]')
        );

    this.relevant.updateNodes(nodes);
};

const originalInit = Form.prototype.init;
const originalValidateInput = Form.prototype.validateInput;

Form.prototype.evaluationCascadeAdditions = [
    constraintUpdate,
    relevantErrorUpdate,
];

Form.prototype.init = function () {
    const that = this;
    let initialized = false;

    // Before any other change handlers, add the "strict check" handlers
    if (settings.strictViolationSelector) {
        this.view.$.on(
            'change.file',
            'input:not(.ignore)[data-required][data-oc-required-type="strict"], select:not(.ignore)[data-required][data-oc-required-type="strict"], textarea:not(.ignore)[data-required][data-oc-required-type="strict"]',
            function (evt) {
                if (initialized) {
                    that.strictRequiredCheckHandler(evt, this);
                }
            }
        ).on(
            'change.file',
            'input:not(.ignore)[data-constraint][data-oc-constraint-type="strict"], select:not(.ignore)[data-constraint][data-oc-constraint-type="strict"], textarea:not(.ignore)[data-constraint][data-oc-constraint-type="strict"]',
            function (evt) {
                if (initialized) {
                    that.strictConstraintCheckHandler(evt, this);
                }
            }
        );
    }

    const loadErrors = originalInit.call(this);

    initialized = true;

    return loadErrors;
};

Form.prototype.specialOcLoadValidate = function (includeRequired) {
    const that = this;
    let $collectionToValidate = this.getRelatedNodes('data-constraint');

    if (includeRequired) {
        $collectionToValidate = $collectionToValidate.add(
            this.getRelatedNodes('data-required')
        );
    }

    // Note, even if includeRequired is falsy, any empty question that has both a required and constraint expression
    // will show a required error.
    // So the above collection determining is just to limit the amount of validation the engine has to perform but it
    // still needs cleaning, because the engine will validate **all** expressions on the selected question.

    $collectionToValidate.each(function () {
        const control = this;
        that.validateInput(control).then((result) => {
            if (!result.requiredValid && !includeRequired) {
                // Undo the displaying of a required error message upon load.
                // Note: a failed required means there cannot be a failed constraint, because they are mutually exclusive
                // in the engine (constraint is only evaluated if question has a value).
                that.setValid(control, 'required');
            }
        });
    });
};

// No-op because OC clears non-relevant values immediately. Save a huge amount of time upon 'Close'.
Form.prototype.clearNonRelevant = () => {};

/**
 * Skip constraint (and required) validation if question is currently marked with "invalid-relevant" error.
 *
 * @param {[type]} $input - [description]
 * @param control
 * @return {[type]}        [description]
 */
Form.prototype.validateInput = function (control) {
    // There is a condition where a value change results in both an invalid-relevant and invalid-constraint,
    // where the invalid constraint is added *after* the invalid-relevant. I can reproduce in automated test (not manually).
    // It is probably related due to the asynchronicity of the constraint evaluation.
    //
    // To crudely resolve this, we remove any constraint error here.
    // However we do want some of the other things that validateInput does (ie. updating the required "*" visibility), so
    // we will still run it but then remove any invalid classes.
    //
    // This is very unfortunate, but these are the kind of acrobatics that are necessary to "fight" the built-in behavior of Enketo's form engine.
    return originalValidateInput.call(this, control).then((result) => {
        if (
            result &&
            (!result.requiredValid ||
                !result.constraintValid || // form.validateInput still returns true for hidden and disabled fields
                (Array.isArray(result.constraintValid) &&
                    result.constraintValid.some((valid) => valid === false)))
        ) {
            const question = control.closest('.question');
            if (question && question.classList.contains('invalid-relevant')) {
                // TODO: another perhaps more efficient approach would be to check with invalid-constraintN classes are currently present
                Form.constraintNames.forEach((constraint) =>
                    this.setValid(control, constraint)
                );
            }
        }

        return result;
    });
};

Form.prototype.strictRequiredCheckHandler = function (evt, input) {
    const that = this;
    const n = {
        path: this.input.getName(input),
        required: this.input.getRequired(input),
        val: this.input.getVal(input),
    };

    // No need to validate.
    if (
        n.readonly ||
        n.inputType === 'hidden' ||
        input.closest('.invalid-relevant')
    ) {
        return;
    }

    // Only now, will we determine the index (expensive).
    n.ind = this.input.getIndex(input);

    // Check required
    if (n.val === '' && this.model.node(n.path, n.ind).isRequired(n.required)) {
        const question = input.closest('.question');
        const msg = question.querySelector('.or-required-msg.active').innerHTML;
        gui.alertStrictError(msg);
        // Cancel propagation input
        evt.stopImmediatePropagation();
        const currentModelValue = that.model.node(n.path, n.ind).getVal();
        that.input.setVal(input, currentModelValue);
        // When changing this make sure that the radio picker's change
        // listener picks this event up.
        // https://github.com/OpenClinica/enketo-express-oc/issues/168
        input.dispatchEvent(events.InputUpdate());
        question.scrollIntoView();
    }
};

Form.prototype.strictConstraintCheckHandler = function (evt, input) {
    const that = this;
    const n = {
        path: this.input.getName(input),
        xmlType: this.input.getXmlType(input),
        constraint: this.input.getConstraint(input)[0],
        val: this.input.getVal(input),
    };

    // No need to validate.
    if (
        n.readonly ||
        n.inputType === 'hidden' ||
        !n.constraint ||
        input.closest('.invalid-relevant')
    ) {
        return;
    }

    // Only now, will we determine the index (expensive).
    n.ind = this.input.getIndex(input);

    // In order to evaluate the constraint, its value has to be set in the model.
    // This would trigger a fieldsubmission, which is what we're trying to prevent.
    // A heavy-handed dumb-but-safe approach is to clone the model and set the value there.
    const modelClone = new FormModel(
        new XMLSerializer().serializeToString(this.model.xml)
    );
    // TODO: initialize clone with **external data**.
    modelClone.init();
    // Set the value in the clone
    const updated = modelClone.node(n.path, n.ind).setVal(n.val, n.xmlType);
    // Check if strict constraint passes
    if (!updated) {
        return;
    }
    // Note: we don't use Enketo Core's nodeset.validateConstraintAndType here because it's asynchronous,
    // which means we couldn't selectively stop event propagation.
    const modelCloneNodeValue = modelClone.node(n.path, n.ind).getVal();

    if (modelCloneNodeValue.toString() === '') {
        return;
    }

    if (
        typeof n.constraint !== 'undefined' &&
        n.constraint !== null &&
        n.constraint.length > 0 &&
        !modelClone.evaluate(n.constraint, 'boolean', n.path, n.ind)
    ) {
        const question = input.closest('.question');
        const msg = question.querySelector(
            '.or-constraint-msg.active'
        ).innerHTML;
        gui.alertStrictError(msg);
        // Cancel propagation input
        evt.stopImmediatePropagation();
        const currentModelValue = that.model.node(n.path, n.ind).getVal();
        that.input.setVal(input, currentModelValue);
        // When changing this make sure that the radio picker's change
        // listener picks this event up.
        // https://github.com/OpenClinica/enketo-express-oc/issues/168
        input.dispatchEvent(events.InputUpdate());
        question.scrollIntoView();
    }
};

/**
 * Removes an invalid mark on a question in the form UI.
 * OC: customized to also work on groups
 *
 * @param {Element} control - form control HTML element
 * @param {string} [type] - One of "constraint", "required" and "relevant".
 */
Form.prototype.setValid = function (control, type) {
    const wrap = control.closest(
        '.question, .calculation, .or-group, .or-group-data'
    );

    if (!wrap) {
        // TODO: this condition occurs, at least in tests for itemsets, but we need find out how.
        return;
    }

    const classes = type
        ? [`invalid-${type}`]
        : [...wrap.classList].filter((cl) => cl.indexOf('invalid-') === 0);
    wrap.classList.remove(...classes);
};

/**
 * Marks a question as invalid in the form UI.
 * OC: customized to also work on groups
 *
 * @param {Element} control - form control HTML element
 * @param {string} [type] - One of "constraint", "required" and "relevant".
 */
Form.prototype.setInvalid = function (control, type = 'constraint') {
    const wrap = control.closest(
        '.question, .calculation, .or-group, .or-group-data'
    );

    if (!wrap) {
        // TODO: this condition occurs, at least in tests for itemsets, but we need find out how.
        return;
    }

    if (config.validatePage === false && this.isValid(control)) {
        this.blockPageNavigation();
    }

    wrap.classList.add(`invalid-${type}`);
};

// TODO can this function be removed entirely?
/**
 * Checks whether the question is not currently marked as invalid. If no argument is provided, it checks the whole form.
 * OC customization: added group
 *
 * @param {Element} node - form control HTML element
 * @return {!boolean} Whether the question/form is not marked as invalid.
 */
Form.prototype.isValid = function (node) {
    const invalidSelectors = ['.invalid-required', '.invalid-relevant'].concat(
        this.constraintClassesInvalid
    );
    if (node) {
        const questionOrGroup = node.closest(
            '.question, .calculation, .or-group, .or-group-data'
        );
        const cls = questionOrGroup.classList;

        return !invalidSelectors.some((selector) => cls.contains(selector));
    }

    return !this.view.html.querySelector(invalidSelectors.join(', '));
};

/**
 *
 * @param {*} control - form control HTML element
 * @param {*} result - result object obtained from Nodeset.validate
 */
Form.prototype.updateValidityInUi = function (control, result) {
    let passed = result.requiredValid !== false;

    // if required === false, result.constraintValid returned by nodeset.validate is null
    if (result.constraintValid === null) {
        result.constraintValid = Array.from(Array(21)).map(() => null);
    }

    // Update UI
    if (result.requiredValid === false) {
        if (Array.isArray(result.constraintValid)) {
            result.constraintValid.forEach((valid, index) =>
                this.setValid(
                    control,
                    index === 0 ? 'constraint' : `constraint${index}`
                )
            );
        }
        this.setInvalid(control, 'required');
    } else {
        this.setValid(control, 'required');

        if (Array.isArray(result.constraintValid)) {
            result.constraintValid.forEach((valid, index) => {
                const cls = index === 0 ? 'constraint' : `constraint${index}`;
                if (valid === false) {
                    passed = false;
                    this.setInvalid(control, cls);
                } else {
                    // includes valid === undefined
                    this.setValid(control, cls);
                }
            });
        }
    }

    if (!passed) {
        control.dispatchEvent(events.Invalidated());
    }
};

/* eslint import/prefer-default-export: "off" */
export { Form };
<|endoftext|>


<commit_before>var fs = require("fs")
var path = require("path")
var env = require("process").env
var async = require("gens")
var both = require("gens/both")

// ensureDirectory := () => Continuable<String>
// ensureDirectory returns the directory where profiles are stored
module.exports = async(ensureDirectory)

function* ensureDirectory() {
    var configDir = path.join(home(), ".config")
    var configDirStat = yield both(fs.stat.bind(null, configDir))

    if (configDirStat[0]) {
        yield fs.mkdir.bind(null, configDir)
    }

    var processDashDir = path.join(configDir, processDashDirectory())
    var processDashDirStat = yield both(fs.stat.bind(null, processDashDir))

    if (processDashDirStat[0]) {
        yield fs.mkdir.bind(null, processDashDir)
    }

    return processDashDir
}

function processDashDirectory() {
    return env.processDash_DIRECTORY || "process-dash"
}

function home() {
    return env.HOME || env.HOMEPATH || env.USERPROFILE
}
<commit_msg>fix bug<commit_after>var fs = require("fs")
var path = require("path")
var env = require("process").env
var async = require("gens")
var both = require("gens/both")

// ensureDirectory := () => Continuable<String>
// ensureDirectory returns the directory where profiles are stored
module.exports = async(ensureDirectory)

function* ensureDirectory() {
    var configDir = path.join(home(), ".config")
    var configDirStat = yield both(fs.stat.bind(null, configDir))

    if (configDirStat[0]) {
        yield fs.mkdir.bind(null, configDir)
    }

    var processDashDir = path.join(configDir, processDashDirectory())
    var processDashDirStat = yield both(fs.stat.bind(null, processDashDir))

    if (processDashDirStat[0]) {
        yield fs.mkdir.bind(null, processDashDir)
    }

    return processDashDir
}

function processDashDirectory() {
    return env.PDASH_DIRECTORY || "process-dash"
}

function home() {
    return env.HOME || env.HOMEPATH || env.USERPROFILE
}
<|endoftext|>


<commit_before>window.MediaListItemView = Backbone.View.extend({
    tagName: "tr",
    initialize: function () {
        this.model.bind("change",  this.render, this);
        this.model.bind("destroy", this.remove, this);
        this.model.bind("all", function (event) {console.log ('an item view got', event)}, this);
    },
    events: {
//        "click" : "onClick"
    },
    render: function () {
        console.log ("rendering item:", this.model);
        $(this.el).html(template.item(this.model.toJSON()));
        return this;
    },
    onClick: function () {
//        $('.media-name', this.el).click();
        console.log($('a', this.el));

    },
   // Remove this view from the DOM.
   remove: function() {
     $(this.el).remove();
   },

   // Remove the item, destroy the model.
   clear: function() {
     this.model.destroy();
   }

});

var leadingZero = function(num){
    return (num < 10) ? "0"+num : num;
}

var toMili = function (time) {
    var t = time.match(/(\d{2}):(\d{2}):(\d{2})\.(\d*)/);
    t.shift();
    var n, m = parseInt(t.pop());
    var f = [24, 60, 60, 100];
    var F = 1;

    while (n = parseInt(t.pop())) {
        F = F * f.pop();
        m += n * F;
    }

    return m;
};
var prettyTime =  function (m) {
    var t = [];
    var f = [24, 60, 60, 100];

    while (F = f.pop()) {
        t.push(m - Math.floor(m/F)*F);
        m = Math.floor(m/F)
    }

    var mili = t.shift();
    t.reverse();
    t = _.map (t, function (e) {return leadingZero(e);});
    var s = t.join(':');
    return s +  '.' + mili.toString();
};

window.MediaListView = Backbone.View.extend({
    el: $("#content"),
    initialize: function () {
        console.log ("init...");
/*
        var medias = this.collection.models;
        var mediaNames = _.map(medias, function (w) {return w.attributes.file;});
        $('#search', this.el).html(new SearchView({source : mediaNames,
                                                   target : '.table'}).render().el);
        $('#playbar', this.el).html(new PlayBarView({model : appModel}).render().el);
*/
        var self = this;
        self.collection = this.model.get('collection');

        $(this.el).html(template.medialist(this.model.toJSON()));
        if (! this.options.dragSource) {
            self.prepareSortable();
        }

        new SearchView({el: $('#media-search', this.el),
                        collection: self.collection,
                        field  : 'file',
                        target : $('#table', this.el)});

        window.socket.on('medias:moved', function (move) {
            self.moveDOM(move.id, move.from, move.to);
            self.collection.move(move.from, move.to);
        });

        _.bindAll(this,
                  'renderModel',
                  'addOne',
                  'addOneAnim',
                  'addAll',
                  'checkEmpty',
                  'updateTotalTime',
                  'prepareSortable');
//        mediaList.bind('change', this.renderMe, this);
        self.collection.bind('add',   this.addOneAnim, this);
        self.collection.bind('reset', this.addAll, this);
        self.collection.bind('remove',this.checkEmpty, this);
        self.collection.bind('all',   function (e, a) {console.log('got: ' + e, a);}, this);
        self.collection.bind('all',   this.updateTotalTime, this);
        self.collection.bind('update',this.update, this);

        this.addAll();
        this.updateTotalTime();
//        this.render();
    },
    prepareSortable: function () {
        var self = this;
        $('.delete-drop', self.el).sortable({
            connectWith: '.connected-sortable',
            update : function (e, ui) {
                self.collection.remove (self.collection.get(ui.item[0].id));
                ui.item.remove();
            },
        }).hide();
        $('.tbody', this.el).addClass('recieve-drag').sortable({
            connectWith: '.connected-sortable',
            helper: 'clone',
            forceHelperSize : true,
            forcePlaceholderSize : true,
            revert : true,
            start: function (e, ui) {
                /**
                 * HUGE HACK: we only get this set when the helper
                 * comes from a draggable, this allows us to make a
                 * difference, to show or not the 'delete' div based on
                 * weather we come from our sortable (i.e. this), or
                 * from somewhere else (i.e. the other pane)
                 */
                console.log ('start');
                if (ui.helper[0].className.match('ui-draggable-dragging'))
                    return;
                $('.delete-drop').show()
            },
            stop: function (e, ui) {
                console.log ('stop');
                $('.delete-drop').hide()
                if (! self.nextDrop) /* we are dropping internally */
                    return;

                var drop = mediaList.get(self.nextDrop);
                $(ui.item).addClass("handleMe");

                _($(this).children()).each (function (order, index) {
                    if (order.classList.contains("handleMe")) {
                        if (self.collection.indexed_id)
                            self.collection.indexed_id (drop, index);
                        var item = self.collection.index_add (drop.attributes, {at: index, silent: true});
                        $(order).animate({
                            'opacity': 0,
                        }, 500, function () {
                            $(order).replaceWith(self.renderModel (item));
                        });
                    } else if (order.id == 'empty-alert') { /* placeholder */
                        $(order).animate({
                            'opacity': 0,
                        }, 500, function () {
                            $(order).remove();
                        });
                    }
                });
                self.nextDrop = false;
                console.log('col is now', self.collection.models);
                self.collection.trigger ('change');
            },
            /**
             * This is not ready. what we need to do is disable the add
             * event we get, but still handle re-order in the model, without
             * triggering a reset. all this while listening (and queuing)
             * other add events, (that may come from other sources
             **/
            receive: function( event, ui ) {
                console.log ('receive', $(ui), ui.sender[0].getAttribute('id'), this.lastDrop);
                self.nextDrop =  ui.sender[0].getAttribute('id');
            },
            update: function (e, ui) {
                var dragged_id = ui.item[0].id;
                _($(this).children()).each(function (order, index) {
                    var media = self.collection.get(order);
                    if (media && media.get(media.idAttribute) == dragged_id) {
                        var move = {id: dragged_id, from: media.get('pos'), to: index}
                        console.log (move);
                        window.socket.emit('medias:moved', move);
                        self.collection.move(move.from, move.to);
                        self.updateTotalTime();
                        return;
                    }
                });
            },
        });
    },
    updateTotalTime: function () {
        console.log ('update time', this.collection.pluck('duration'));
        var totalTime = _.reduce(this.collection.pluck('duration'), function (m, n) {
            console.log (n); return m + toMili(n);}, 0);
        $('.total-time', this.el)[0].textContent = prettyTime(totalTime);
        console.log ('Total Time: ', prettyTime(totalTime));
    },
    update: function(){
        this.collection.sort()
    },
    renderModel: function (media) {
        var item = new MediaListItemView({model: media}).render().el;
        item.setAttribute ("id", media.get('_id'));

        if (this.options.dragSource) {
            $(item).draggable({//revert: true,
                               helper: 'clone',
                               cursorAt: { top: -5, left: -5 },
                               connectToSortable: ".recieve-drag",
                              });
        }
        return item;
    },
    addOne: function (media) {
        console.log ("adding: ", media.get('file'));
        var item = this.renderModel (media);
        this.$('#media-view', this.el).append(item);
    },
    addOneAnim: function (media) {
        this.addOne(media);

        // ooh, shiny animation!
        this.$('#' + media.id, this.el).css('opacity', 0);
        this.$('#' + media.id, this.el).animate({
            'opacity': 1,
        }, 2000);
    },
    addAll: function() {
        console.log('addALL', this.el);
        if (this.collection.length == 0) {
            this.$('#media-view', this.el).append(this.$('#empty-alert', this.el).clone());
            return
        }
        console.log (this.el, 'empty', this.collection.models);
        this.$('#media-view', this.el).empty();
        this.collection.each(this.addOne);
        console.log('addALL -- end');
    },
    checkEmpty: function () {
        if (this.collection.length == 0) {
            this.$('#media-view', this.el).append(this.$('#empty-alert', this.el).clone());
            return
        }
    },
    render: function () {
        console.log ("render...");

//        $(this.el).append(new Paginator({model: this.model, page: this.options.page}).render().el);

        return this;
    }
});
<commit_msg>add some time function (auto update)<commit_after>window.MediaListItemView = Backbone.View.extend({
    tagName: "tr",
    initialize: function () {
        this.model.bind("change",  this.render, this);
        this.model.bind("destroy", this.remove, this);
        this.model.bind("all", function (event) {console.log ('an item view got', event)}, this);
    },
    events: {
//        "click" : "onClick"
    },
    render: function () {
        console.log ("rendering item:", this.model);
        $(this.el).html(template.item(this.model.toJSON()));
        return this;
    },
    onClick: function () {
//        $('.media-name', this.el).click();
        console.log($('a', this.el));

    },
   // Remove this view from the DOM.
   remove: function() {
     $(this.el).remove();
   },

   // Remove the item, destroy the model.
   clear: function() {
     this.model.destroy();
   }

});

var leadingZero = function(num){
    return (num < 10) ? "0"+num : num;
}

var toMili = function (time) {
    var t = time.match(/(\d{2}):(\d{2}):(\d{2})\.(\d*)/);
    t.shift();
    var n, m = parseInt(t.pop());
    var f = [24, 60, 60, 100];
    var F = 1;

    while (n = parseInt(t.pop())) {
        F = F * f.pop();
        m += n * F;
    }

    return m;
};
var prettyTime =  function (m) {
    var t = [];
    var f = [24, 60, 60, 100];

    while (F = f.pop()) {
        t.push(m - Math.floor(m/F)*F);
        m = Math.floor(m/F)
    }

    var mili = t.shift();
    t.reverse();
    t = _.map (t, function (e) {return leadingZero(e);});
    var s = t.join(':');
    return s +  '.' + leadingZero(mili);
};

window.MediaListView = Backbone.View.extend({
    el: $("#content"),
    initialize: function () {
        console.log ("init...");
/*
        var medias = this.collection.models;
        var mediaNames = _.map(medias, function (w) {return w.attributes.file;});
        $('#search', this.el).html(new SearchView({source : mediaNames,
                                                   target : '.table'}).render().el);
        $('#playbar', this.el).html(new PlayBarView({model : appModel}).render().el);
*/
        var self = this;
        self.collection = this.model.get('collection');

        $(this.el).html(template.medialist(this.model.toJSON()));
        if (! this.options.dragSource) {
            self.prepareSortable();
        }

        new SearchView({el: $('#media-search', this.el),
                        collection: self.collection,
                        field  : 'file',
                        target : $('#table', this.el)});

        window.socket.on('medias:moved', function (move) {
            self.moveDOM(move.id, move.from, move.to);
            self.collection.move(move.from, move.to);
        });

        _.bindAll(this,
                  'renderModel',
                  'addOne',
                  'addOneAnim',
                  'addAll',
                  'checkEmpty',
                  'updateTotalTime',
                  'prepareSortable');
//        mediaList.bind('change', this.renderMe, this);
        self.collection.bind('add',   this.addOneAnim, this);
        self.collection.bind('reset', this.addAll, this);
        self.collection.bind('remove',this.checkEmpty, this);
        self.collection.bind('all',   function (e, a) {console.log('got: ' + e, a);}, this);
        self.collection.bind('all',   this.updateTotalTime, this);
        self.collection.bind('update',this.update, this);

        this.addAll();
        this.updateTotalTime();
//        this.render();
    },
    prepareSortable: function () {
        var self = this;
        $('.delete-drop', self.el).sortable({
            connectWith: '.connected-sortable',
            update : function (e, ui) {
                self.collection.remove (self.collection.get(ui.item[0].id));
                ui.item.remove();
            },
        }).hide();
        $('.tbody', this.el).addClass('recieve-drag').sortable({
            connectWith: '.connected-sortable',
            helper: 'clone',
            forceHelperSize : true,
            forcePlaceholderSize : true,
            revert : true,
            start: function (e, ui) {
                /**
                 * HUGE HACK: we only get this set when the helper
                 * comes from a draggable, this allows us to make a
                 * difference, to show or not the 'delete' div based on
                 * weather we come from our sortable (i.e. this), or
                 * from somewhere else (i.e. the other pane)
                 */
                console.log ('start');
                if (ui.helper[0].className.match('ui-draggable-dragging'))
                    return;
                $('.delete-drop').show()
            },
            stop: function (e, ui) {
                console.log ('stop');
                $('.delete-drop').hide()
                if (! self.nextDrop) /* we are dropping internally */
                    return;

                var drop = mediaList.get(self.nextDrop);
                $(ui.item).addClass("handleMe");

                _($(this).children()).each (function (order, index) {
                    if (order.classList.contains("handleMe")) {
                        if (self.collection.indexed_id)
                            self.collection.indexed_id (drop, index);
                        var item = self.collection.index_add (drop.attributes, {at: index, silent: true});
                        $(order).animate({
                            'opacity': 0,
                        }, 500, function () {
                            $(order).replaceWith(self.renderModel (item));
                        });
                    } else if (order.id == 'empty-alert') { /* placeholder */
                        $(order).animate({
                            'opacity': 0,
                        }, 500, function () {
                            $(order).remove();
                        });
                    }
                });
                self.nextDrop = false;
                console.log('col is now', self.collection.models);
                self.collection.trigger ('change');
            },
            /**
             * This is not ready. what we need to do is disable the add
             * event we get, but still handle re-order in the model, without
             * triggering a reset. all this while listening (and queuing)
             * other add events, (that may come from other sources
             **/
            receive: function( event, ui ) {
                console.log ('receive', $(ui), ui.sender[0].getAttribute('id'), this.lastDrop);
                self.nextDrop =  ui.sender[0].getAttribute('id');
            },
            update: function (e, ui) {
                var dragged_id = ui.item[0].id;
                _($(this).children()).each(function (order, index) {
                    var media = self.collection.get(order);
                    if (media && media.get(media.idAttribute) == dragged_id) {
                        var move = {id: dragged_id, from: media.get('pos'), to: index}
                        console.log (move);
                        window.socket.emit('medias:moved', move);
                        self.collection.move(move.from, move.to);
                        self.updateTotalTime();
                        return;
                    }
                });
            },
        });
    },
    updateTotalTime: function () {
        console.log ('update time', this.collection.pluck('duration'));
        var totalTime = _.reduce(this.collection.pluck('duration'), function (m, n) {
            console.log (n); return m + toMili(n);}, 0);
        $('.total-time', this.el)[0].textContent = prettyTime(totalTime);
        console.log ('Total Time: ', prettyTime(totalTime));
    },
    update: function(){
        this.collection.sort()
    },
    renderModel: function (media) {
        var item = new MediaListItemView({model: media}).render().el;
        item.setAttribute ("id", media.get('_id'));

        if (this.options.dragSource) {
            $(item).draggable({//revert: true,
                               helper: 'clone',
                               cursorAt: { top: -5, left: -5 },
                               connectToSortable: ".recieve-drag",
                              });
        }
        return item;
    },
    addOne: function (media) {
        console.log ("adding: ", media.get('file'));
        var item = this.renderModel (media);
        this.$('#media-view', this.el).append(item);
    },
    addOneAnim: function (media) {
        this.addOne(media);

        // ooh, shiny animation!
        this.$('#' + media.id, this.el).css('opacity', 0);
        this.$('#' + media.id, this.el).animate({
            'opacity': 1,
        }, 2000);
    },
    addAll: function() {
        console.log('addALL', this.el);
        if (this.collection.length == 0) {
            this.$('#media-view', this.el).append(this.$('#empty-alert', this.el).clone());
            return
        }
        console.log (this.el, 'empty', this.collection.models);
        this.$('#media-view', this.el).empty();
        this.collection.each(this.addOne);
        console.log('addALL -- end');
    },
    checkEmpty: function () {
        if (this.collection.length == 0) {
            this.$('#media-view', this.el).append(this.$('#empty-alert', this.el).clone());
            return
        }
    },
    render: function () {
        console.log ("render...");

//        $(this.el).append(new Paginator({model: this.model, page: this.options.page}).render().el);

        return this;
    }
});
<|endoftext|>


<commit_before>var gl;
var program;

// points registered
var reg_points = [0, 0,
                  0, 0];

// our canvas
var canvas;

// vertices
var vertices = new Float32Array(reg_points);

// colors
// we seperate color and vertex vectors because it'll be simpler to modify
// them on the fly (hopefully)
var colors = new Float32Array([1, 0, 0, 1,
                               1, 0, 0, 1,
                               1, 0, 0, 1,
                               1, 0, 0, 1 ]);

window.onload = function init()
{
    // loop our render function
    // get canvas and set up webGL
    canvas = document.getElementById("gl-canvas");
    gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL isn't available!"); }

    // add event listeners to canvas
    canvas.addEventListener("mousedown", function(event) {
        //reg_points[0] = event.clientX;
        //reg_points[1] = event.clientY;
        reg_points[0] = normValue(event.clientX, 0, canvas.width, -1, 1);
        reg_points[1] = normValue(event.clientY, 0, canvas.height, -1, 1);
    });

    canvas.addEventListener("mouseup", function(event) {
        //reg_points[2] = event.clientX;
        //reg_points[3] = event.clientY;
        reg_points[2] = normValue(event.clientX, 0, canvas.width, -1, 1);
        reg_points[3] = normValue(event.clientY, 0, canvas.height, -1, 1);

        vertices = new Float32Array(makeSquare(reg_points));

        // Load colors into the GPU and associate shader variables
        var cBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, colors, gl.DYNAMIC_DRAW);

        var vColor = gl.getAttribLocation(program, "vColor");
        gl.vertexAttribPointer(vColor, 4, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(vColor);

        // Load positions into the GPU and associate shader variables
        var bufferId = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

        var vPosition = gl.getAttribLocation(program, "vPosition");
        gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(vPosition);

        // clear canvas
        gl.clear(gl.COLOR_BUFFER_BIT);

        // re-register vertices in buffer
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

        // draw our square
        //gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
        gl.drawArrays(gl.LINE_LOOP, 0, 4);
        console.log(reg_points);
        console.log(vertices);
    });

    // configure viewport
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(1.0, 1.0, 1.0, 1.0);

    // init shader program and bind it
    program = initShaders(gl, "vertex-shader", "fragment-shader");

    gl.useProgram(program);

    vertices = new Float32Array(makeSquare(reg_points));
};

function normValue(value, valueMin, valueMax, resultMin, resultMax)
{
    temp = (value - valueMin) / (valueMax - valueMin);
    return temp * (resultMax - resultMin) + resultMin;
}

function makeSquare(arr)
{
    // assuming the first point's x and y are both lower than
    // the second point's x and y, the positions are..
    temp = [arr[0], -arr[1], // bottom left
            arr[2], -arr[1], // bottom right
            arr[2], -arr[3], // top right
            arr[0], -arr[3]]; // top left
    /* note: we invert the y-coordinates because the canvas
       and webgl have different coordinate conventions;
       canvas has its origin (0,0) at the top left corner, while
       webgl has its origin in the center.
    */

    return temp;
}
<commit_msg>implement color changing<commit_after>var gl;

// points registered
var reg_points = [0, 0,
                  0, 0];

// our canvas
var canvas;

// vertices
var vertices = new Float32Array(reg_points);

// colors
// we seperate color and vertex vectors because it'll be simpler to modify
// them on the fly (hopefully)
var colors = new Float32Array([1, 0, 0, 1,
                               1, 0, 0, 1,
                               1, 0, 0, 1,
                               1, 0, 0, 1 ]);

var _red = new Float32Array([1, 0, 0, 1,
                             1, 0, 0, 1,
                             1, 0, 0, 1,
                             1, 0, 0, 1 ]);

var _green = new Float32Array([0, 1, 0, 1,
                               0, 1, 0, 1,
                               0, 1, 0, 1,
                               0, 1, 0, 1 ]);

var _blue = new Float32Array([0, 0, 1, 1,
                              0, 0, 1, 1,
                              0, 0, 1, 1,
                              0, 0, 1, 1 ]);

// fetch the keycodes we'll need to change color
// we do this now so we don't have to do it every
// time a key is pressed
var r_code = "r".charCodeAt(0);
var g_code = "g".charCodeAt(0);
var b_code = "b".charCodeAt(0);

window.onload = function init()
{
    // loop our render function
    // get canvas and set up webGL
    canvas = document.getElementById("gl-canvas");
    gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL isn't available!"); }

    // the canvas can't be focused; thus we need to add a key listener
    // to the window instead
    window.addEventListener("keypress", function(event) {
        switch(event.charCode) {
            case r_code:
                colors = _red;
                break;
            case g_code:
                colors = _green;
                break;
            case b_code:
                colors = _blue;
                break;
        }

        // Load colors into the GPU and associate shader variables
        var cBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, cBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, colors, gl.DYNAMIC_DRAW);

        var vColor = gl.getAttribLocation(program, "vColor");
        gl.vertexAttribPointer(vColor, 4, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(vColor);
    });

    // add event listeners to canvas
    canvas.addEventListener("mousedown", function(event) {
        //reg_points[0] = event.clientX;
        //reg_points[1] = event.clientY;
        reg_points[0] = normValue(event.clientX, 0, canvas.width, -1, 1);
        reg_points[1] = normValue(event.clientY, 0, canvas.height, -1, 1);
    });

    canvas.addEventListener("mouseup", function(event) {
        //reg_points[2] = event.clientX;
        //reg_points[3] = event.clientY;
        reg_points[2] = normValue(event.clientX, 0, canvas.width, -1, 1);
        reg_points[3] = normValue(event.clientY, 0, canvas.height, -1, 1);

        vertices = new Float32Array(makeSquare(reg_points));

        // Load positions into the GPU and associate shader variables
        var bufferId = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

        var vPosition = gl.getAttribLocation(program, "vPosition");
        gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(vPosition);

        // clear canvas
        gl.clear(gl.COLOR_BUFFER_BIT);

        // re-register vertices in buffer
        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

        // draw our square
        //gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
        gl.drawArrays(gl.LINE_LOOP, 0, 4);
    });

    // configure viewport
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(1.0, 1.0, 1.0, 1.0);

    // init shader program and bind it
    var program = initShaders(gl, "vertex-shader", "fragment-shader");

    gl.useProgram(program);
    // Load colors into the GPU and associate shader variables
    var cBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.DYNAMIC_DRAW);

    var vColor = gl.getAttribLocation(program, "vColor");
    gl.vertexAttribPointer(vColor, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(vColor);

    vertices = new Float32Array(makeSquare(reg_points));
};

function normValue(value, valueMin, valueMax, resultMin, resultMax)
{
    temp = (value - valueMin) / (valueMax - valueMin);
    return temp * (resultMax - resultMin) + resultMin;
}

function makeSquare(arr)
{
    // assuming the first point's x and y are both lower than
    // the second point's x and y, the positions are..
    temp = [arr[0], -arr[1], // bottom left
            arr[2], -arr[1], // bottom right
            arr[2], -arr[3], // top right
            arr[0], -arr[3]]; // top left
    /* note: we invert the y-coordinates because the canvas
       and webgl have different coordinate conventions;
       canvas has its origin (0,0) at the top left corner, while
       webgl has its origin in the center.
    */

    return temp;
}
<|endoftext|>


<commit_before>
/**
 * Island
 */
function Island(id,  size, from)
{
    this.id     = id;
    this.size   = size;
    this.from   = [from[0], from[1]];
    this.to     = [this.from[0] + size, this.from[1] + size];
    this.bodies = new Collection([], 'id', true);
}

/**
 * Add body
 *
 * @param {Array} body
 */
Island.prototype.addBody = function(body)
{
    this.bodies.add(body);
    body.islands.add(this);
};

/**
 * Remove body
 *
 * @param {Array} body
 */
Island.prototype.removeBody = function(body)
{
    this.bodies.remove(body);
    body.islands.remove(this);
};

/**
 * Add body
 *
 * @param {Array} body
 */
Island.prototype.testBody = function(body)
{
    return this.getBody(body) === null;
};

/**
 * Add body
 *
 * @param {Body} body
 */
Island.prototype.getBody = function(body)
{
    if (this.bodyInBound(body, this.from, this.to)) {
        for (var i = this.bodies.items.length - 1; i >= 0; i--) {
            if (this.bodiesTouch(this.bodies.items[i], body)) {
                return this.bodies.items[i];
            }
        }
    }

    return null;
};

/**
 * Bodies touch
 *
 * @param {Body} bodyA
 * @param {Body} bodyB
 *
 * @return {Boolean}
 */
Island.prototype.bodiesTouch = function(bodyA, bodyB)
{
    return this.getDistance(bodyA.position, bodyB.position) < (bodyA.radius + bodyB.radius) && bodyA.matchMask(bodyB);
};

/**
 * Is point in bound?
 *
 * @param {Body} body
 * @param {Array} from
 * @param {Array} to
 *
 * @return {Boolean}
 */
Island.prototype.bodyInBound = function(body, from, to)
{
    return body.position[0] + body.radius > from[0] &&
           body.position[0] - body.radius < to[0]   &&
           body.position[1] + body.radius > from[1] &&
           body.position[1] - body.radius < to[1];
};

/**
 * get Distance
 *
 * @param {Array} from
 * @param {Array} to
 *
 * @return {Number}
 */
Island.prototype.getDistance = function(from, to)
{
    return Math.sqrt(Math.pow(from[0] - to[0], 2) + Math.pow(from[1] - to[1], 2));
};

/**
 * Random Position
 *
 * @param {Number} radius
 * @param {Number} border
 *
 * @return {Array}
 */
Island.prototype.prototype.getRandomPosition = function(radius, border)
{
    var margin = radius + border * this.size,
        point = this.getRandomPoint(margin);

    while (!this.testBody(point, margin)) {
        point = this.getRandomPoint(margin);
    }

    return point;
};

/**
 * Get random point
 *
 * @param {Number} margin
 *
 * @return {Array}
 */
Island.prototype.getRandomPoint = function(margin)
{
    return [
        margin + Math.random() * (this.size - margin * 2),
        margin + Math.random() * (this.size - margin * 2)
    ];
};

/**
 * Clear the world
 */
Island.prototype.clear = function()
{
    this.bodies.clear();
};<commit_msg>Fixed typo<commit_after>
/**
 * Island
 */
function Island(id,  size, from)
{
    this.id     = id;
    this.size   = size;
    this.from   = [from[0], from[1]];
    this.to     = [this.from[0] + size, this.from[1] + size];
    this.bodies = new Collection([], 'id', true);
}

/**
 * Add body
 *
 * @param {Array} body
 */
Island.prototype.addBody = function(body)
{
    this.bodies.add(body);
    body.islands.add(this);
};

/**
 * Remove body
 *
 * @param {Array} body
 */
Island.prototype.removeBody = function(body)
{
    this.bodies.remove(body);
    body.islands.remove(this);
};

/**
 * Add body
 *
 * @param {Array} body
 */
Island.prototype.testBody = function(body)
{
    return this.getBody(body) === null;
};

/**
 * Add body
 *
 * @param {Body} body
 */
Island.prototype.getBody = function(body)
{
    if (this.bodyInBound(body, this.from, this.to)) {
        for (var i = this.bodies.items.length - 1; i >= 0; i--) {
            if (this.bodiesTouch(this.bodies.items[i], body)) {
                return this.bodies.items[i];
            }
        }
    }

    return null;
};

/**
 * Bodies touch
 *
 * @param {Body} bodyA
 * @param {Body} bodyB
 *
 * @return {Boolean}
 */
Island.prototype.bodiesTouch = function(bodyA, bodyB)
{
    return this.getDistance(bodyA.position, bodyB.position) < (bodyA.radius + bodyB.radius) && bodyA.matchMask(bodyB);
};

/**
 * Is point in bound?
 *
 * @param {Body} body
 * @param {Array} from
 * @param {Array} to
 *
 * @return {Boolean}
 */
Island.prototype.bodyInBound = function(body, from, to)
{
    return body.position[0] + body.radius > from[0] &&
           body.position[0] - body.radius < to[0]   &&
           body.position[1] + body.radius > from[1] &&
           body.position[1] - body.radius < to[1];
};

/**
 * get Distance
 *
 * @param {Array} from
 * @param {Array} to
 *
 * @return {Number}
 */
Island.prototype.getDistance = function(from, to)
{
    return Math.sqrt(Math.pow(from[0] - to[0], 2) + Math.pow(from[1] - to[1], 2));
};

/**
 * Random Position
 *
 * @param {Number} radius
 * @param {Number} border
 *
 * @return {Array}
 */
Island.prototype.getRandomPosition = function(radius, border)
{
    var margin = radius + border * this.size,
        point = this.getRandomPoint(margin);

    while (!this.testBody(point, margin)) {
        point = this.getRandomPoint(margin);
    }

    return point;
};

/**
 * Get random point
 *
 * @param {Number} margin
 *
 * @return {Array}
 */
Island.prototype.getRandomPoint = function(margin)
{
    return [
        margin + Math.random() * (this.size - margin * 2),
        margin + Math.random() * (this.size - margin * 2)
    ];
};

/**
 * Clear the world
 */
Island.prototype.clear = function()
{
    this.bodies.clear();
};<|endoftext|>


<commit_before>// ==UserScript==
// @name            A Dark Room - Helper
// @namespace       http://github.com/taeram/user-scripts
// @description     Helper for A Dark Room
// @match           http://adarkroom.doublespeakgames.com/
// @grant           none
// @copyright       Jesse Patching
// @version         2.3.0
// @license         MIT https://github.com/taeram/user-scripts/blob/master/LICENSE
// @updateURL       https://raw.github.com/taeram/user-scripts/master/dist/a-dark-room.user.js
// @downloadURL     https://raw.github.com/taeram/user-scripts/master/dist/a-dark-room.user.js
// ==/UserScript==

(function() {
    'use strict';
    var ADR = {
        intervals: {},
        resources: {},
        elements: {},
        weapons: {},

        stokeFire: function () {
            $('#stokeButton').click();
        },

        gatherWood: function ()  {
            $('#gatherButton').click();
        },

        checkTraps: function ()  {
            $('#trapsButton').click();
        },

        countResources: function () {
            var stores = $('#stores .storeRow');
            for (var i = 0; i < stores.length; i++) {
                var store = stores[i];
                ADR.resources[$(store).find('> .row_key').text()] = parseInt($(store).find('> .row_val').text(), 10);
            }

            var weapons = $('#weapons .storeRow');
            for (i = 0; i < weapons.length; i++) {
                var weapon = weapons[i];
                ADR.weapons[$(weapon).find('> .row_key').text()] = parseInt($(weapon).find('> .row_val').text(), 10);
            }
        },

        hasEnoughResources: function(buttonEl) {
            ADR.countResources();

            // Figure out how many resources we need to purchase this thing
            var requiredResources = {};
            var resourceTooltips = $(buttonEl).find('.tooltip .row_key,.row_val');
            for (var j = 0; j < resourceTooltips.length; j += 2) {
                requiredResources[$(resourceTooltips[j]).text()] = parseInt($(resourceTooltips[j + 1]).text(), 10);
            }

            // Do we have enough resources to purchase this?
            for (var key in requiredResources) {
                if (ADR.resources[key] < requiredResources[key]) {
                    return false;
                }
            }

            return true;
        },

        balanceWorkers: function () {
            var resourceLimits = {
                bait: 1000,
                bullets: 1000,
                coal: 1000,
                "cured meat": 1000,
                iron: 1000,
                leather: 1000,
                fur: 1000,
                meat: 1000,
                steel: 1000,
                sulphur: 1000,
                wood: 1000,
            };

            var productionPerInterval = {
            };

            var workers = {};
            var rows = $('#workers .workerRow');
            for (var i = 0; i < rows.length; i++) {
                var name = $(rows[i]).attr('key');
                var count = $(rows[i]).find('.row_val > span:first-child').text();
                var resources = {};

                var resourceRows = $(rows[i]).find('.tooltip .storeRow');
                for (var j = 0; j < resourceRows.length; j++) {
                    var key = $(resourceRows[j]).find('.row_key').text();
                    var matches = $(resourceRows[j]).find('.row_val').text().match(/^(.+?) per (.+)s$/);
                    var value = parseInt(matches[1], 10);
                    var interval = parseInt(matches[2], 10);

                    resources[key] = {
                        value: value,
                        interval: interval
                    };

                    if (typeof productionPerInterval[key] === 'undefined') {
                        productionPerInterval[key] = value;
                    } else {
                        productionPerInterval[key] += value;
                    }
                }

                workers[name] = {
                    count: count,
                    resources: resources
                };
            }

            if (typeof ADR.elements.production_per_interval === 'undefined') {
                ADR.elements.production_per_interval = $('

').appendTo('#workers');
            }
            $(ADR.elements.production_per_interval).empty();
            for (var key in productionPerInterval) {
                $(ADR.elements.production_per_interval).append('
' + productionPerInterval[key] + '

' + key + '
');
            }
        },

        buildThings: function () {
            ADR.countResources();

            // Build all the things
            var things = $('#buildBtns .button');
            for (var i=0; i < things.length; i++) {
                if ($(things[i]).hasClass('disabled')) {
                    continue;
                }

                if (ADR.hasEnoughResources(things[i])) {
                    $(things[i]).click();
                }
            }

            // Craft most of the things
            var items = $('#craftBtns .button');
            for (i = 0; i < items.length; i++) {
                if ($(items[i]).hasClass('disabled')) {
                    continue;
                }

                var itemName = $(items[i]).attr('id').replace(/build_/, '');

                // Always have torches on hand
                if (itemName == 'torch' && ADR.resources.torch >= 10) {
                    continue;
                }

                // Only buy one of each weapon
                if (ADR.weapons[itemName] >= 1) {
                    continue;
                }

                if (ADR.hasEnoughResources(items[i])) {
                    $(items[i]).click();
                }
            }

            // Buy a few things
            var purchases = $('#buyBtns .button');
            for (i = 0; i < purchases.length; i++) {
                if ($(purchases[i]).hasClass('disabled')) {
                    continue;
                }

                var purchaseName = $(purchases[i]).attr('id').replace(/build_/, '');
                var purchaseItem = false;

                // Buy a compass
                if (purchaseName == 'compass') {
                    purchaseItem = true;
                }

                // Always have bolas
                if (purchaseName == 'bolas' && ADR.weapons.bolas <= 10) {
                    purchaseItem = true;
                }

                // Always have bullets
                if (purchaseName == 'bullets' && ADR.resources.bullets <= 50) {
                    purchaseItem = true;
                }

                if (purchaseItem && ADR.hasEnoughResources(purchases[i])) {
                    $(purchases[i]).click();
                }
            }
        },

        handleEvent: function () {
            if ($('#event').length > 0) {
                // Allow the old wanderer to stay the night
                if ($('#agree').length > 0 || $('#buyMap').length > 0) {
                    $('#agree').click();

                    // Learn scouting
                    $('#learn').click();

                    // Buy a shit load of maps
                    if ($('#buyMap').length > 0) {
                        while (!$('#buyMap').hasClass('disabled')) {
                            $('#buyMap').click();
                        }
                    }

                    $('#leave').click();
                    $('#deny').click();
                }

                // Spare the thief
                if ($('#spare').length > 0) {
                    $('#spare').click();
                    $('#leave').click();
                }

                // Mourn the dead
                if ($('#mourn').length > 0) {
                    $('#mourn').click();
                }

                // Track the prints into the forest
                if ($('#track').length > 0) {
                    $('#track').click();
                    $('#end').click();
                }

                // Investigate the scratching noises
                if ($('#investigate').length > 0) {
                    $('#investigate').click();
                    $('#leave').click();
                }

                // Give the beggar some furs
                if ($('#100furs').length > 0) {
                    $('#100furs').click();
                    $('#50furs').click();
                    $('#leave').click();
                    $('#deny').click();
                }

                // Give the beggar some furs
                if ($('#fur100').length > 0) {
                    $('#fur500').click();
                    $('#fur100').click();
                    $('#leave').click();
                    $('#deny').click();
                }

                // Give the wanderer some wood
                if ($('#wood500').length > 0) {
                    $('#wood500').click();
                    $('#wood100').click();
                    $('#leave').click();
                    $('#deny').click();
                }

                // Give the man some medicine
                if ($('#help').length > 0) {
                    $('#help').click();
                    $('#bye').click();
                }

                // Buy medicine to prevent the plague from spreading
                if ($('#buyMedicine').length > 0) {
                    $('#heal').click();
                    $('#buyMedicine').click();
                    $('#ignore').click();
                }

                // Buy a compass from the nomad
                if ($('#buyCompass').length > 0) {
                    $('#buyCompass').click();
                    $('#goodbye').click();
                }

                // Grab a trait
                if ($('#evasion').length > 0) {
                    $('#evasion').click();
                    $('#precision').click();
                    $('#force').click();
                    $('#nothing').click();
                }

                // Go home after the beasts attack
                if ($('#end').length > 0) {
                    $('#end').click();
                }

                // Go back inside
                if ($('#backinside').length > 0) {
                    $('#backinside').click();
                }

                // Loot from an outpost and leave
                if ($('.eventTitle').text() === 'An Outpost') {
                    $('#loot_takeEverything').click();
                    $('#leave').click();
                }
            }
        },

        handleFighting: function () {
            if ($('#event').length > 0) {
                $('.weaponButton').click();
            }

            // Eat meat.
            let $eat = $('#healButtons #eat');
            if ($eat.hasClass('disabled')) {
                $eat = null;
            }

            let hp = ADR.getHp();
            if ($eat && hp.current < (hp.max - World.MEAT_HEAL)) {
                console.log("Eating Meat");
                $('#healButtons #eat').click();
            }

            // Use medicine.
            let $meds = $('#healButtons #meds');
            if ($meds.hasClass('disabled')) {
                $meds = null;
            }

            hp = ADR.getHp();
            if ($meds && (hp.current < (hp.max - World.MEDS_HEAL) || !$eat && hp.current <= hp.max / 2)) {
                console.log("Using meds");
                $('#healButtons #meds').click();
            }
        },

        getHp: function () {
            let currentHp = null;
            let maxHp = null;

            let hp = $('#wanderer .hp').text();
            if (hp.length > 0) {
                hp = hp.match(/(\d+)\/(\d+)/)
                if (hp.length > 0) {
                    currentHp = hp[1];
                    maxHp = hp[2];
                }
            }

            return {
                'current': currentHp,
                'max': maxHp
            };
        }
    };

    // Keep that fire stoked
    ADR.intervals.stokeFire = setInterval(ADR.stokeFire, 300000);
    ADR.stokeFire();

    // Gather resources
    ADR.intervals.gatherWood = setInterval(ADR.gatherWood, 1000);
    ADR.intervals.checkTraps = setInterval(ADR.checkTraps, 1000);

    // Build all the things
    ADR.intervals.buildThings = setInterval(ADR.buildThings, 30000);
    ADR.buildThings();

    // Balance the workers
    ADR.intervals.balanceWorkers = setInterval(ADR.balanceWorkers, 1000);
    ADR.balanceWorkers();

    // Handle any incoming events
    ADR.intervals.handleEvent = setInterval(ADR.handleEvent, 1000);

    // Handle any fighting
    ADR.intervals.handleFighting = setInterval(ADR.handleFighting, 250);
})();
<commit_msg>Only attack an enemy if their HP > 0<commit_after>// ==UserScript==
// @name            A Dark Room - Helper
// @namespace       http://github.com/taeram/user-scripts
// @description     Helper for A Dark Room
// @match           http://adarkroom.doublespeakgames.com/
// @grant           none
// @copyright       Jesse Patching
// @version         2.4.0
// @license         MIT https://github.com/taeram/user-scripts/blob/master/LICENSE
// @updateURL       https://raw.github.com/taeram/user-scripts/master/a-dark-room.user.js
// @downloadURL     https://raw.github.com/taeram/user-scripts/master/a-dark-room.user.js
// ==/UserScript==

(function() {
    'use strict';
    var ADR = {
        intervals: {},
        resources: {},
        elements: {},
        weapons: {},

        stokeFire: function () {
            $('#stokeButton').click();
        },

        gatherWood: function ()  {
            $('#gatherButton').click();
        },

        checkTraps: function ()  {
            $('#trapsButton').click();
        },

        countResources: function () {
            var stores = $('#stores .storeRow');
            for (var i = 0; i < stores.length; i++) {
                var store = stores[i];
                ADR.resources[$(store).find('> .row_key').text()] = parseInt($(store).find('> .row_val').text(), 10);
            }

            var weapons = $('#weapons .storeRow');
            for (i = 0; i < weapons.length; i++) {
                var weapon = weapons[i];
                ADR.weapons[$(weapon).find('> .row_key').text()] = parseInt($(weapon).find('> .row_val').text(), 10);
            }
        },

        hasEnoughResources: function(buttonEl) {
            ADR.countResources();

            // Figure out how many resources we need to purchase this thing
            var requiredResources = {};
            var resourceTooltips = $(buttonEl).find('.tooltip .row_key,.row_val');
            for (var j = 0; j < resourceTooltips.length; j += 2) {
                requiredResources[$(resourceTooltips[j]).text()] = parseInt($(resourceTooltips[j + 1]).text(), 10);
            }

            // Do we have enough resources to purchase this?
            for (var key in requiredResources) {
                if (ADR.resources[key] < requiredResources[key]) {
                    return false;
                }
            }

            return true;
        },

        balanceWorkers: function () {
            var resourceLimits = {
                bait: 1000,
                bullets: 1000,
                coal: 1000,
                "cured meat": 1000,
                iron: 1000,
                leather: 1000,
                fur: 1000,
                meat: 1000,
                steel: 1000,
                sulphur: 1000,
                wood: 1000,
            };

            var productionPerInterval = {
            };

            var workers = {};
            var rows = $('#workers .workerRow');
            for (var i = 0; i < rows.length; i++) {
                var name = $(rows[i]).attr('key');
                var count = $(rows[i]).find('.row_val > span:first-child').text();
                var resources = {};

                var resourceRows = $(rows[i]).find('.tooltip .storeRow');
                for (var j = 0; j < resourceRows.length; j++) {
                    var key = $(resourceRows[j]).find('.row_key').text();
                    var matches = $(resourceRows[j]).find('.row_val').text().match(/^(.+?) per (.+)s$/);
                    var value = parseInt(matches[1], 10);
                    var interval = parseInt(matches[2], 10);

                    resources[key] = {
                        value: value,
                        interval: interval
                    };

                    if (typeof productionPerInterval[key] === 'undefined') {
                        productionPerInterval[key] = value;
                    } else {
                        productionPerInterval[key] += value;
                    }
                }

                workers[name] = {
                    count: count,
                    resources: resources
                };
            }

            if (typeof ADR.elements.production_per_interval === 'undefined') {
                ADR.elements.production_per_interval = $('

').appendTo('#workers');
            }
            $(ADR.elements.production_per_interval).empty();
            for (var key in productionPerInterval) {
                $(ADR.elements.production_per_interval).append('
' + productionPerInterval[key] + '

' + key + '
');
            }
        },

        buildThings: function () {
            ADR.countResources();

            // Build all the things
            var things = $('#buildBtns .button');
            for (var i=0; i < things.length; i++) {
                if ($(things[i]).hasClass('disabled')) {
                    continue;
                }

                if (ADR.hasEnoughResources(things[i])) {
                    $(things[i]).click();
                }
            }

            // Craft most of the things
            var items = $('#craftBtns .button');
            for (i = 0; i < items.length; i++) {
                if ($(items[i]).hasClass('disabled')) {
                    continue;
                }

                var itemName = $(items[i]).attr('id').replace(/build_/, '');

                // Always have torches on hand
                if (itemName == 'torch' && ADR.resources.torch >= 10) {
                    continue;
                }

                // Only buy one of each weapon
                if (ADR.weapons[itemName] >= 1) {
                    continue;
                }

                if (ADR.hasEnoughResources(items[i])) {
                    $(items[i]).click();
                }
            }

            // Buy a few things
            var purchases = $('#buyBtns .button');
            for (i = 0; i < purchases.length; i++) {
                if ($(purchases[i]).hasClass('disabled')) {
                    continue;
                }

                var purchaseName = $(purchases[i]).attr('id').replace(/build_/, '');
                var purchaseItem = false;

                // Buy a compass
                if (purchaseName == 'compass') {
                    purchaseItem = true;
                }

                // Always have bolas
                if (purchaseName == 'bolas' && ADR.weapons.bolas <= 10) {
                    purchaseItem = true;
                }

                // Always have bullets
                if (purchaseName == 'bullets' && ADR.resources.bullets <= 50) {
                    purchaseItem = true;
                }

                if (purchaseItem && ADR.hasEnoughResources(purchases[i])) {
                    $(purchases[i]).click();
                }
            }
        },

        handleEvent: function () {
            if ($('#event').length > 0) {
                // Allow the old wanderer to stay the night
                if ($('#agree').length > 0 || $('#buyMap').length > 0) {
                    $('#agree').click();

                    // Learn scouting
                    $('#learn').click();

                    // Buy a shit load of maps
                    if ($('#buyMap').length > 0) {
                        while (!$('#buyMap').hasClass('disabled')) {
                            $('#buyMap').click();
                        }
                    }

                    $('#leave').click();
                    $('#deny').click();
                }

                // Spare the thief
                if ($('#spare').length > 0) {
                    $('#spare').click();
                    $('#leave').click();
                }

                // Mourn the dead
                if ($('#mourn').length > 0) {
                    $('#mourn').click();
                }

                // Track the prints into the forest
                if ($('#track').length > 0) {
                    $('#track').click();
                    $('#end').click();
                }

                // Investigate the scratching noises
                if ($('#investigate').length > 0) {
                    $('#investigate').click();
                    $('#leave').click();
                }

                // Give the beggar some furs
                if ($('#100furs').length > 0) {
                    $('#100furs').click();
                    $('#50furs').click();
                    $('#leave').click();
                    $('#deny').click();
                }

                // Give the beggar some furs
                if ($('#fur100').length > 0) {
                    $('#fur500').click();
                    $('#fur100').click();
                    $('#leave').click();
                    $('#deny').click();
                }

                // Give the wanderer some wood
                if ($('#wood500').length > 0) {
                    $('#wood500').click();
                    $('#wood100').click();
                    $('#leave').click();
                    $('#deny').click();
                }

                // Give the man some medicine
                if ($('#help').length > 0) {
                    $('#help').click();
                    $('#bye').click();
                }

                // Buy medicine to prevent the plague from spreading
                if ($('#buyMedicine').length > 0) {
                    $('#heal').click();
                    $('#buyMedicine').click();
                    $('#ignore').click();
                }

                // Buy a compass from the nomad
                if ($('#buyCompass').length > 0) {
                    $('#buyCompass').click();
                    $('#goodbye').click();
                }

                // Grab a trait
                if ($('#evasion').length > 0) {
                    $('#evasion').click();
                    $('#precision').click();
                    $('#force').click();
                    $('#nothing').click();
                }

                // Go home after the beasts attack
                if ($('#end').length > 0) {
                    $('#end').click();
                }

                // Go back inside
                if ($('#backinside').length > 0) {
                    $('#backinside').click();
                }

                // Loot from an outpost and leave
                if ($('.eventTitle').text() === 'An Outpost') {
                    $('#loot_takeEverything').click();
                    $('#leave').click();
                }
            }
        },

        handleFighting: function () {
            let enemyHp = ADR.getEnemyHp();
            if ($('#event').length > 0 && enemyHp.current > 0) {
                $('.weaponButton').click();
                $('#attack_bolas').click();
            }

            // Eat meat.
            let $eat = $('#healButtons #eat');
            if ($eat.hasClass('disabled')) {
                $eat = null;
            }

            let hp = ADR.getHp();
            if ($eat && hp.current < (hp.max - World.MEAT_HEAL)) {
                console.log("Eating Meat");
                $('#healButtons #eat').click();
            }

            // Use medicine.
            let $meds = $('#healButtons #meds');
            if ($meds.hasClass('disabled')) {
                $meds = null;
            }

            hp = ADR.getHp();
            if ($meds && (hp.current < (hp.max - World.MEDS_HEAL) || !$eat && hp.current <= hp.max / 2)) {
                console.log("Using meds");
                $('#healButtons #meds').click();
            }
        },

        getHp: function () {
            let currentHp = null;
            let maxHp = null;

            let hp = $('#wanderer .hp').text();
            if (hp.length > 0) {
                hp = hp.match(/(\d+)\/(\d+)/)
                if (hp.length > 0) {
                    currentHp = hp[1];
                    maxHp = hp[2];
                }
            }

            return {
                'current': currentHp,
                'max': maxHp
            };
        },

        getEnemyHp: function () {
            let currentHp = null;
            let maxHp = null;

            let hp = $('#enemy .hp').text();
            if (hp.length > 0) {
                hp = hp.match(/(\d+)\/(\d+)/)
                if (hp.length > 0) {
                    currentHp = hp[1];
                    maxHp = hp[2];
                }
            }

            return {
                'current': currentHp,
                'max': maxHp
            };
        }
    };

    // Keep that fire stoked
    ADR.intervals.stokeFire = setInterval(ADR.stokeFire, 300000);
    ADR.stokeFire();

    // Gather resources
    ADR.intervals.gatherWood = setInterval(ADR.gatherWood, 1000);
    ADR.intervals.checkTraps = setInterval(ADR.checkTraps, 1000);

    // Build all the things
    ADR.intervals.buildThings = setInterval(ADR.buildThings, 30000);
    ADR.buildThings();

    // Balance the workers
    ADR.intervals.balanceWorkers = setInterval(ADR.balanceWorkers, 1000);
    ADR.balanceWorkers();

    // Handle any incoming events
    ADR.intervals.handleEvent = setInterval(ADR.handleEvent, 1000);

    // Handle any fighting
    ADR.intervals.handleFighting = setInterval(ADR.handleFighting, 250);
})();
<|endoftext|>


<commit_before>var uuid = require('node-uuid');

var userSchema = require('./../schemata/user');
userSchema.method('login', function(data, providers, callback) {
  var user = this;
  var authorizer = providers[data.provider].authorizer;
  authorizer(data.uid, data.pw)(function (err, res) {
    if (err || !res) {
      callback(new Error("Login Failed"));
    } else {
      user.session = {
        provider: data.provider,
        uid: data.uid
      }
      user.token = uuid.v4();
      user.save(function () {
        callback(null)
      });
    }
  })
});

module.exports = require('mongoose').model('User', userSchema);

<commit_msg>find or create by authorizations on mongoose model<commit_after>var uuid = require('node-uuid');

var userSchema = require('./../schemata/user');
/*
 * A user will login with local credentials (provider=local)
 * or with 3rd party credentials (provider=github) in either case
 * we want to findOrCreate a User and grant a fresh token. */
userSchema.statics.findOrCreateByAuthorization = function(data, providers, callback) {
  var authorizer = providers[data.provider].authorizer;
  authorizer(data.uid, data.pw)(function (err, providerData) {
    if (err) {
      callback(new Error("Login Failed"));
    } else {
      if (data.provider === 'local') {
        var user = providerData.user;
        user.token = uuid.v4();
        user.save(function () {
          callback(null, user)
        });
      } else {
        this.findOne()
        .where('authorizations.'+data.provider+'.login')
        .equals(providerData.login)
        .exec(function (err, user) {
          if (err) { return callback(err); }
          else if (user) {
            // Found the user
            if (! user.authorizations) user.authorizations = {};
            user.authorizations[data.provider] = providerData;
            user.token = uuid.v4();
            user.save(function () {
              callback(null, user)
            });
          } else {
            // Create the user
            var user = new User();
            if (! user.authorizations) user.authorizations = {};
            user.authorizations[data.provider] = providerData;
            user.token = uuid.v4();
            user.save(function () {
              callback(null, user)
            });
          }
        })
      }
    }
  }.bind(this))
};

var User = require('mongoose').model('User', userSchema);

module.exports = User;

<|endoftext|>