<commit_before>/* ************************************************************************

    License: MIT Licence

    Dependencies : [
        a.js
        plugin/language.js (optional)
    ]

    Events : []

    Description:
        Manipulate HTML form by with a simple system

************************************************************************ */

/**
 * Manipulate HTML form by with a simple system
 *
 * Examples: here
 *
 * @class form
 * @static
 * @namespace a
*/
a.form = (function() {
    "use strict";

    // HTML/HTML5 input type allowed224
    var typePatternList = ["text", "search", "url", "tel", "email", "password"],
        minMaxStepList = ["number", "range", "date", "datetime", "datetime-local", "month", "time", "week"],
        typeRequiredList = typePatternList.concat(minMaxStepList, ["number", "checkbox", "radio", "file"]),
        typeMultipleList = ["email", "file"],
        typeList = minMaxStepList.concat(typePatternList, ["color", "checkbox", "file", "hidden", "radio"]);

    /**
     * Get the attribute from it's name
     *
     * @method __attr
     * @private
     *
     * @param el {DOMElement} An element to get hash from
     * @param attr {String | null} The attribute to search for that element (default : see __attr)
     * @return {String | null} The hash tag associated (if found)
    */
    function __attr(el, attr) {
        return (!a.isNone(el.getAttribute)) ? el.getAttribute(attr) : null;
    };

    /**
     * Convert an HTMLCollection to array
     *
     * @method __htmlCollectionToArray
     * @private
     *
     * @param el {HTMLCollection} The collection to convert
     * @return {Array} An array version
    */
    function __htmlCollectionToArray(el) {
        var ret = [],
            i   = el.length;
        while(i--) {
            ret.push(el[i]);
        }
        return ret;
    };

    /**
     * Get the field key from given input
     *
     * @method __getFieldKey
     * @private
     *
     * @param e {DOMElement} The element o search value inside
     * @return {String} The value found
    */
    function __getFieldKey(e) {
        var name  = __attr(e, "name");

        // Search the good attribute in case of problem
        if(a.isNone(name) || name === "") {
            name = __attr(e, "id");
            // Should never appear... But we provide it in case of trouble
            if(a.isNone(name) || name === "") {
                name = __attr(e, "class");
            }
        }

        return name;
    };

    /**
     * Get the field value for given input
     *
     * @method __getFieldValue
     * @private
     *
     * @param e {DOMElement} The element to search value inside
     * @return {String} The value found
    */
    function __getFieldValue(e){
        var type    = __attr(e, "type"),
            tagName = e.tagName.toLowerCase();

        if(tagName === "input" || tagName === "textarea") {
            return (type === "checkbox") ? e.checked : e.value;
        } else if(tagName === "select") {
            return e.options[e.selectedIndex].value;
        }
    };

    /**
     * From a given dom, get the list of elements inside
     *
     * @method __getFieldList
     * @private
     *
     * @param dom {DOMElement} The dom element to search inside
     * @return {Array} The element list inside DOM
    */
    function __getFieldList(dom) {
        var inputList     = dom.getElementsByTagName("input"),
            textAreaList  = dom.getElementsByTagName("textarea"),
            selectList    = dom.getElementsByTagName("select"),
            outputList    = [];

        // We remove input who are not listed in typeList (like submit should not appear here)
        inputList = __htmlCollectionToArray(inputList);

        var i = inputList.length;
        while(i--) {
            var at = __attr(inputList[i], "type");
            // We allow null type to be consider as valid (as it will be consider as "text" type by most of browsers)
            if(!a.contains(typeList, at) && !a.isNone(at)) {
                inputList.splice(i, 1);
            }
        }

        // Concat everything
        return inputList.concat(
            __htmlCollectionToArray(textAreaList),
            __htmlCollectionToArray(selectList)
        );
    };

    /**
     * Raise an error on input
     *
     * @method __validateError
     * @private
     *
     * @param el {DOMElement} The element where comes from error
     * @param id {String} The element id/name/class
     * @param name {String | null} The name (like min, pattern, ...) which is not valid, can be null
     * @param value {String | null} The current input value (can be used as parameter
     * @return {Object} A validate object with everything inside if possible
    */
    function __validateError(el, id, name, value) {
        // First : we need to get error element and translate if possible
        var error = "";

        // Retrieve error tag
        if(!a.isNone(name) && name !== "") {
            error = __attr(el, "data-error-" + name);
        }
        if(a.isNone(error) || error === "") {
            error = __attr(el, "data-error");
        }

        if(a.isNone(error) || error === "") {
            a.console.warn("A data-error tag has not been setted for " + id + " with value " + value + ", can't proceed error show", 3);
        }

        // Translate error if possible
        if(!a.isNone(a.language)) {
            error = a.language.getSingleTranslation(error, {
                name : name,
                value : value
            });
        }

        // Returning an object with all needed data inside
        return {
            el : el,
            id : id,
            error : error
        };
    };

    return {
        /**
         * Allow to skip HTML5 form-novalidate tag or not (boolean)
         *
         * @property skipNoValidate
         * @type Boolean
         * @default false
        */
        skipNoValidate : false,

        /**
         * Get the list of element stored into given form
         *
         * @method get
         *
         * @param dom {DOMElement} The dom element to search inside (basically it should be document.getElementById("myFormId"))
         * @return {Object} The list of input tags existing
        */
        get : function(dom) {
            var inputList  = __getFieldList(dom),
                outputList = {};

            var i = inputList.length;
            while(i--) {
                var input = inputList[i];

                var name  = __getFieldKey(input),
                    value = __getFieldValue(input);

                // We don't continue if we don't find any data on element
                if(name === null || name === "") {
                    continue;
                }

                // We got a special case with input radio type
                if(!a.isNone(input) && __attr(input, "type") === "radio") {
                    // Only checked one are validated
                    if(input.checked) {
                        outputList[name] = (value === "") ? null : value;
                    }
                } else {
                    outputList[name] = (value === "") ? null : value;
                }
            };

            a.console.log("a.form.get: found element list:", 3);
            a.console.log(outputList, 3);
            return outputList;
        },

        /**
         * Validate a form
         * Note : multiple tester (email, file) is not supported
         * Note : date field (date, datetime, datetime-local, month, time, week) are not supported
         * Note : tel/file field are not supported
         *
         * @method validate
         *
         * @param dom {DOMElement} The dom element to search inside (basically it should be document.getElementById("myFormId"))
         * @return {Array} An array with all errors listed inside, an empty array if there is no error to show
        */
        validate : function(dom) {
            // On form tag, the "novalidate" allow to not validate a form
            if(!a.isNone(__attr(dom, "novalidate")) && this.skipNoValidate === false) {
                return [];
            }

            var inputList    = __getFieldList(dom),
                // Store all errors appearing
                errorList    = [],
                // TODO : after todos are ended, remove this...
                allowedTypes = ["number", "range", "text", "search", "url", "email", "password", "color", "checkbox", "hidden", "radio"],
                // Pretty basic url tester
                urlTester    = new RegExp("^[a-z]{2,}:\\/\\/([a-z0-9\\/\\.\\-_~+;:&=]{2,})$", "gi"),
                // Pretty basic email tester
                emailTester  = new RegExp("^.{2,}@.*\\.[a-z0-9]{2,}$", "gi"),
                colorTester  = new RegExp("^#([a-f]{3}|[a-f]{6})$", "gi");

            /*
             * required : at least one char (text, search, url, tel, email, password, date, datetime, datetime-local, month, time, week, number, checkbox, radio, file)
             * pattern : a regex to test (Use title like a helper), (text, search, url, tel, email, password)
             * multiple : the user is allowed to enter more than one element (only for email, file)
             * min/max : min/max value (number, range, date, datetime, datetime-local, month, time, week)
             * step : multiplicateur (The step attribute works with the following input types: number, range, date, datetime, datetime-local, month, time, week)
            */
            var i = inputList.length;
            while(i--) {
                // Does only work for input tags
                var el      = inputList[i],
                    tagName = el.tagName.toLowerCase();

                // formnovalidate : we must not validate this element (including all select)
                if(tagName === "select" || !a.isNone(__attr(el, "formnovalidate"))) {
                    continue;
                }

                var type     = __attr(el, "type"),
                    name    = __getFieldKey(el),
                    value    = el.value,

                    required = __attr(el, "required"),
                    pattern  = __attr(el, "pattern"),
                    multiple = __attr(el, "multiple"),
                    min      = __attr(el, "min"),
                    max      = __attr(el, "max"),
                    step     = __attr(el, "step");

                // Double check float data
                min  = (a.isNone(min) || min === "")   ? null : parseFloat(min);
                max  = (a.isNone(max) || max === "")   ? null : parseFloat(max);
                step = (a.isNone(step) || step === "") ? null : parseFloat(step);

                // Check input type does existing in allowed type list
                if(tagName === "input" && !a.contains(allowedTypes, type) && !a.isNone(type)) {
                    a.console.warn("Type " + type + " for input " + name + " not recognized or not supported", 3);
                    continue;
                }

                // Now checking type
                if( (type === "number" || type === "range") && !a.isNumber(value) ) {
                    errorList.push(__validateError(el, name, null, value));
                    continue;
                }
                if(type === "url" && !urlTester.test(value) ) {
                    errorList.push(__validateError(el, name, null, value));
                    continue;
                }
                if(type === "email" && !emailTester.test(value) ) {
                    errorList.push(__validateError(el, name, null, value));
                    continue;
                }
                if(type === "color" && !colorTester.test(value) ) {
                    errorList.push(__validateError(el, name, null, value));
                    continue;
                }

                // Required test
                if( required !== null && a.contains(typeRequiredList, type) && (value === "" || a.isNone(value)) ) {
                    errorList.push(__validateError(el, name, "required", value));
                    continue;
                }

                // Pattern test
                if( pattern !== null && (tagName === "textarea" || (a.contains(typePatternList, type)) || a.isNone(type)) ) {
                    var reg = new RegExp(pattern);
                    if(!reg.test(value)) {
                        errorList.push(__validateError(el, name, "pattern", value));
                        continue;
                    }
                }

                // Min/max/step test
                if( (min !== null || max != null || step != null) && a.contains(minMaxStepList, type) ) {
                    var pval = parseFloat(value);
                    if( min !== null && pval < min ) {
                        errorList.push(__validateError(el, name, "min", value));
                        continue;
                    }
                    if( max !== null && pval > max ) {
                        errorList.push(__validateError(el, name, "max", value));
                        continue;
                    }
                    if( step !== null && pval % step !== 0 ) {
                        errorList.push(__validateError(el, name, "step", value));
                        continue;
                    }
                }
            }

            a.console.log("a.form.validate: found error list:", 3);
            a.console.log(errorList, 3);
            return errorList;
        },

        /**
         * Validate and get the form content
         *
         * @method validateAndGet
         *
         * @param dom {DOMElement} The dom element to search inside (basically it should be document.getElementById("myFormId"))
         * @return {Object} An object with error (boolean), errorList (Array), and contentList (Array)
        */
        validateAndGet : function(dom) {
            var obj = {
                errorList   : this.validate(dom),
                error       : false,
                contentList : this.get(dom)
            };
            if(obj.errorList.length > 0) {
                obj.error = true;
            }
            return obj;
        }
    };

})();<commit_msg>Rework form.js to use a.dom system.<commit_after>/* ************************************************************************

    License: MIT Licence

    Dependencies : [
        a.js
        plugin/language.js (optional)
    ]

    Events : []

    Description:
        Manipulate HTML form by with a simple system.

************************************************************************ */

/**
 * Manipulate HTML form by with a simple system.
 *
 * Examples: here
 *
 * @class form
 * @static
 * @namespace a
*/
a.form = (function() {
    'use strict';

    // HTML/HTML5 input type allowed
    var typePatternList  = ['text', 'search', 'url',
                                    'tel', 'email', 'password'],
        minMaxStepList   = ['number', 'range', 'date',
                                    'datetime', 'datetime-local',
                                    'month', 'time', 'week'],
        typeRequiredList = typePatternList.concat(minMaxStepList,
                                    ['number', 'checkbox', 'radio', 'file']),
        typeMultipleList = ['email', 'file'],
        typeList = minMaxStepList.concat(typePatternList,
                                    ['color', 'checkbox', 'file',
                                    'hidden', 'radio']);

    /**
     * Get the field key from given input.
     *
     * @method getFieldKey
     * @private
     *
     * @param e {DOMElement}                The element o search value inside
     * @return {String}                     The value found
    */
    function getFieldKey(e) {
        var el   = a.dom.el(e),
            name = el.attribute('name');

        // Search the good attribute in case of problem
        if(a.isNone(name) || name === '') {
            name = el.attribute('id');
            // Should never appear... But we provide it in case of trouble
            if(a.isNone(name) || name === '') {
                name = el.attribute('class');
            }
        }

        return name;
    };

    /**
     * Get the field value for given input.
     *
     * @method getFieldValue
     * @private
     *
     * @param e {DOMElement}                The element to search value inside
     * @return {String}                     The value found
    */
    function getFieldValue(e) {
        var type    = e.type || '',
            tagName = e.tagName.toLowerCase();

        if(tagName === 'input' || tagName === 'textarea') {
            return (type === 'checkbox') ? e.checked : e.value;
        } else if(tagName === 'select') {
            return e.options[e.selectedIndex].value;
        }
    };

    /**
     * From a given dom, get the list of revelant elements inside.
     *
     * @method getFieldList
     * @private
     *
     * @param dom {a.dom}                   The dom element to search inside
     * @return {Array}                      The element list inside DOM
    */
    function getFieldList(dom) {
        // dom must be a a.dom element
        var elements = dom.tag(['input', 'textarea', 'select']).getElements();

        // We remove input who are not listed in typeList
        // LIKE: submit/reset should not appear in this list
        var i = elements.length;
        while(i--) {
            var el = elements[i];
            if(el.type &&
                    (  el.type == 'submit'
                    || el.type == 'reset'
                    || el.type == 'image'
                    ) ) {
                elements.splice(i, 1);
            }
        }

        // Now filtering is done, we can send back all elements
        return elements;
    };

    /**
     * Raise an error on input.
     *
     * @method validateError
     * @private
     *
     * @param el {DOMElement}               The element where comes from error
     * @param id {String}                   The element id/name/class
     * @param name {String | null}          The name (like min, pattern, ...)
     *                                      which is not valid, can be null
     * @param value {String | null}         The current input value
     *                                      (can be used as parameter)
     * @return {Object}                     A validate object with everything
     *                                      inside if possible
    */
    function validateError(el, id, name, value) {
        // First : we need to get error element and translate if possible
        var error = '';

        // Retrieve error tag
        if(!a.isNone(name) && name !== '') {
            error = el['data-error-' + name] || null;
        }
        if(a.isNone(error) || error === '') {
            error = el['data-error'] || null;
        }

        if(a.isNone(error) || error === '') {
            var errorMessage  = 'A data-error tag has not been setted for ';
                errorMessage += id + ' with value ' + value + 'n can\'t ';
                errorMessage += 'proceed error message';
            a.console.warn(errorMessage, 3);
        }

        // Translate error if possible
        if(!a.isNone(a.language)) {
            error = a.language.getSingleTranslation(error, {
                name:  name,
                value: value
            });
        }

        // Returning an object with all needed data inside
        return {
            el:    el,
            id:    id,
            error: error
        };
    };

    return {
        /**
         * Allow to skip HTML5 form-novalidate tag or not (boolean)
         *
         * @property skipNoValidate
         * @type Boolean
         * @default false
        */
        skipNoValidate: false,

        /**
         * Get the list of element stored into given form.
         *
         * @method get
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Object}                 The list of input tags existing
        */
        get: function(dom) {
            dom = a.dom.el(dom);
            var inputList  = getFieldList(dom),
                outputList = {};

            var i = inputList.length;
            while(i--) {
                var input = inputList[i];

                var name  = getFieldKey(input),
                    value = getFieldValue(input);

                // We don't continue if we don't find any data on element
                if(a.isNone(name) || !name) {
                    continue;
                }

                var parse = false;

                // We got a special case with input radio type
                if(!a.isNone(input) && input.type === 'radio') {
                    // Only checked one are validated
                    if(input.checked) {
                        parse = true;
                    }
                } else {
                    parse = true;
                }

                if(parse) {
                    outputList[name] = (value === '') ? null : value;
                }
            };

            a.console.log('a.form.get: found element list:', 3);
            a.console.log(outputList, 3);
            return outputList;
        },

        /**
         * Validate a form
         * Note : multiple tester (email, file) is not supported
         * Note : date field (date, datetime, datetime-local,
         * month, time, week) are not supported
         * Note : tel/file field are not supported
         *
         * @method validate
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Array}                  An array with all errors listed
         *                                  inside, an empty array if there
         *                                  is no error to show
        */
        validate: function(dom) {
            dom = a.dom.el(dom);
            // On form tag, the "novalidate" allow to not validate a form
            if(this.skipNoValidate === false &&
                    !a.isNone(dom.get(0).novalidate)) {
                return [];
            }

            var inputList    = getFieldList(dom),
                // Store all errors appearing
                errorList    = [],
                // TODO : after todos are ended, remove this...
                allowedTypes = ['number', 'range', 'text', 'search',
                                        'url', 'email', 'password',
                                        'color', 'checkbox',
                                        'hidden', 'radio'],
                // Pretty basic url tester
                urlTester    = new RegExp(
                    '^[a-z]{2,}:\\/\\/([a-z0-9\\/\\.\\-_~+;:&=]{2,})$', 'gi'),
                // Pretty basic email tester
                emailTester  = new RegExp('^.{2,}@.*\\.[a-z0-9]{2,}$', 'gi'),
                colorTester  = new RegExp('^#([a-f]{3}|[a-f]{6})$', 'gi');

            /*
             * required : at least one char
                (text, search, url, tel, email, password, date, datetime,
                datetime-local, month, time, week, number, checkbox,
                radio, file)
             * pattern : a regex to test (Use title like a helper),
                (text, search, url, tel, email, password)
             * multiple : the user is allowed to enter more than one element
                (only for email, file)
             * min/max : min/max value
                (number, range, date, datetime, datetime-local,
                month, time, week)
             * step : multiplier
                (number, range, date, datetime, datetime-local,
                month, time, week)
            */
            var i = inputList.length;
            while(i--) {
                // Does only work for input tags
                var el      = inputList[i],
                    tagName = el.tagName.toLowerCase();

                // form novalidate : we must not validate
                // this element (including all select)
                if(tagName == 'select'
                    || !a.isNone(el.novalidate)) {
                    continue;
                }

                var type     = el.type,
                    name    = getFieldKey(el),
                    value    = el.value,

                    required = el.required,
                    pattern  = el.pattern,
                    multiple = el.multiple,
                    min      = el.min,
                    max      = el.max,
                    step     = el.step;

                // Double check float data
                min  = (a.isNone(min) || min == '')   ? null :
                                                            parseFloat(min);
                max  = (a.isNone(max) || max == '')   ? null :
                                                            parseFloat(max);
                step = (a.isNone(step) || step == '') ? null :
                                                            parseFloat(step);

                // Check input type does existing in allowed type list
                if(tagName == 'input'
                        && !a.contains(allowedTypes, type)
                        && !a.isNone(type)) {
                    var errorSupport =  'Type ' + type + ' for input ' + name;
                        errorSupport += ' not recognized or not supported';
                    a.console.warn(errorSupport, 3);
                    continue;
                }

                // Now checking type
                if( (type == 'number' || type == 'range')
                        && !a.isNumber(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'url' && !urlTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'email' && !emailTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }
                if(type == 'color' && !colorTester.test(value) ) {
                    errorList.push(validateError(el, name, null, value));
                    continue;
                }

                // Required test
                if( required !== null
                    && a.contains(typeRequiredList, type)
                    && (value === '' || a.isNone(value)) ) {
                    errorList.push(validateError(el, name, 'required', value));
                    continue;
                }

                // Pattern test
                if( pattern !== null
                     && (tagName === "textarea"
                        ||(a.contains(typePatternList, type))
                        || a.isNone(type)
                        )
                ) {
                    var reg = new RegExp(pattern);
                    if(!reg.test(value)) {
                        errorList.push(validateError(
                                            el, name, 'pattern', value));
                        continue;
                    }
                }

                // Min/max/step test
                if( (min !== null || max != null || step != null)
                    && a.contains(minMaxStepList, type) ) {

                    var pval = parseFloat(value);
                    if( min !== null && pval < min ) {
                        errorList.push(validateError(el, name, 'min', value));
                        continue;
                    }
                    if( max !== null && pval > max ) {
                        errorList.push(validateError(el, name, 'max', value));
                        continue;
                    }
                    if( step !== null && pval % step !== 0 ) {
                        errorList.push(validateError(el, name, 'step', value));
                        continue;
                    }
                }
            }

            a.console.log('a.form.validate: found error list:', 3);
            a.console.log(errorList, 3);
            return errorList;
        },

        /**
         * Validate and get the form content.
         *
         * @method validateAndGet
         *
         * @param dom {Object}              The dom element to search inside
         *                                  - It has to be a valid a.dom.el
         *                                  input
         * @return {Object}                 An object with error (boolean),
         *                                  errorList (Array)
         *                                  and contentList (Array)
        */
        validateAndGet: function(dom) {
            var obj = {
                errorList   : this.validate(dom),
                error       : false,
                contentList : this.get(dom)
            };
            if(obj.errorList.length > 0) {
                obj.error = true;
            }
            return obj;
        }
    };

})();<|endoftext|>


<commit_before>/*global $, Whisper, Backbone, textsecure, extension*/
/*
 * vim: ts=4:sw=4:expandtab
 */

// This script should only be included in background.html
(function () {
    'use strict';

    window.Whisper = window.Whisper || {};

    var conversations = new Whisper.ConversationCollection();
    var inboxCollection = new (Backbone.Collection.extend({
        initialize: function() {
            this.on('change:active_at', this.sort);
            this.on('change:unreadCount', this.updateUnreadCount);

            this.listenTo(conversations, 'add change:active_at', this.addActive);
        },
        comparator: function(model) {
            return -model.get('active_at');
        },
        addActive: function(model) {
            if (model.get('active_at')) {
                this.add(model);
            } else {
                this.remove(model);
            }
        },
        updateUnreadCount: function(model, count) {
            var prev = model.previous('unreadCount') || 0;
            if (count < prev) { // decreased
                var newUnreadCount = storage.get("unreadCount", 0) - (prev - count);
                setUnreadCount(newUnreadCount);
                storage.put("unreadCount", newUnreadCount);
            }
        }
    }))();

    // Load the initial set of models for the inbox.
    conversations.fetchActive();

    window.getInboxCollection = function() {
        return inboxCollection;
    };

    window.ConversationController = {
        get: function(id) {
            return conversations.get(id);
        },
        create: function(attrs) {
            if (typeof attrs !== 'object') {
                throw new Error('ConversationController.create requires an object, got', attrs);
            }
            var conversation = conversations.add(attrs);
            return conversation;
        },
        findOrCreatePrivateById: function(id) {
            var conversation = conversations.add({ id: id, type: 'private' });
            return new Promise(function(resolve, reject) {
                conversation.fetch().then(function() {
                    resolve(conversation);
                }).fail(function() {
                    var saved = conversation.save(); // false or indexedDBRequest
                    if (saved) {
                        saved.then(function() {
                            resolve(conversation);
                        }).fail(reject);
                    } else {
                        reject();
                    }
                });
            });
        }
    };
})();
<commit_msg>Contact sync should update existing contacts<commit_after>/*global $, Whisper, Backbone, textsecure, extension*/
/*
 * vim: ts=4:sw=4:expandtab
 */

// This script should only be included in background.html
(function () {
    'use strict';

    window.Whisper = window.Whisper || {};

    var conversations = new Whisper.ConversationCollection();
    var inboxCollection = new (Backbone.Collection.extend({
        initialize: function() {
            this.on('change:active_at', this.sort);
            this.on('change:unreadCount', this.updateUnreadCount);

            this.listenTo(conversations, 'add change:active_at', this.addActive);
        },
        comparator: function(model) {
            return -model.get('active_at');
        },
        addActive: function(model) {
            if (model.get('active_at')) {
                this.add(model);
            } else {
                this.remove(model);
            }
        },
        updateUnreadCount: function(model, count) {
            var prev = model.previous('unreadCount') || 0;
            if (count < prev) { // decreased
                var newUnreadCount = storage.get("unreadCount", 0) - (prev - count);
                setUnreadCount(newUnreadCount);
                storage.put("unreadCount", newUnreadCount);
            }
        }
    }))();

    // Load the initial set of models for the inbox.
    conversations.fetchActive();

    window.getInboxCollection = function() {
        return inboxCollection;
    };

    window.ConversationController = {
        get: function(id) {
            return conversations.get(id);
        },
        create: function(attrs) {
            if (typeof attrs !== 'object') {
                throw new Error('ConversationController.create requires an object, got', attrs);
            }
            var conversation = conversations.add(attrs, {merge: true});
            return conversation;
        },
        findOrCreatePrivateById: function(id) {
            var conversation = conversations.add({ id: id, type: 'private' });
            return new Promise(function(resolve, reject) {
                conversation.fetch().then(function() {
                    resolve(conversation);
                }).fail(function() {
                    var saved = conversation.save(); // false or indexedDBRequest
                    if (saved) {
                        saved.then(function() {
                            resolve(conversation);
                        }).fail(reject);
                    } else {
                        reject();
                    }
                });
            });
        }
    };
})();
<|endoftext|>


<commit_before>'use strict';

import React, {Component} from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    Image,
    TouchableOpacity,
    TextInput,
    BackAndroid
} from 'react-native';
import mqtt from 'react-native-mqtt';
import {statusHeight} from '../common/CommonApi';
import {toastShort} from '../component/Toast';

var agentId = '577ba294280c474b40aad690';
var agentToken = 'WlCllVNCPCZlHlhVyxaNHBiwABdgwuMk';
var SUBSCRIBE_TOPIC = "agents/" + agentId + "/downstream";
var PUBLISH_TOPIC = "jsonUp";
var mqttClient;

class MqttPage extends Component {
    constructor(props)
    {
        super(props);

        var options = {
            host: 'broker.j1st.io',
            port: 1883,
            protocol: 'tcp',
            clientId: agentId,
            user: agentId,
            pass: agentToken,
            tls: false,
            auth: true
        };

        mqtt.createClient(options).then(function (client)
        {
            mqttClient = client;

            client.on('closed', function ()
            {
                console.log('mqtt.event.closed');

            });

            client.on('error', function (msg)
            {
                console.log('mqtt.event.error', msg);

            });

            client.on('message', function (msg)
            {
                console.log('mqtt.event.message', msg);
                toastShort('接收到消息' + JSON.stringify(msg));
            });

            client.on('connect', function ()
            {
                console.log('连接成功');
                client.subscribe(SUBSCRIBE_TOPIC, 0);
                toastShort('连接成功');
            });
        }).catch(function (err)
        {
            console.log(err);
        });
    }

    _connectClient()
    {
        mqttClient.connect();
    }

    _publishClient()
    {
        mqttClient.publish(SUBSCRIBE_TOPIC, 'test', 0, false);
    }

    render()
    {
        return (
            <View style={styles.container}>
                <View style={styles.toolbarStatus}/>
                <Text style={{textAlign: 'center', fontSize: 30}}>Mqtt Sample</Text>
                <View style={{flexDirection: 'row', justifyContent: 'center', marginTop: 40}}>
                    <Text style={{flex: 2, textAlign: 'center'}}>Agent Id</Text>
                    <Text style={{flex: 5, textAlign: 'center'}}>{agentId}</Text>
                </View>
                <View style={{flexDirection: 'row', justifyContent: 'center', marginTop: 40}}>
                    <Text style={{flex: 2, textAlign: 'center'}}>Agent Token</Text>
                    <Text style={{flex: 5, textAlign: 'center'}}>{agentToken}</Text>
                </View>
                <TouchableOpacity onPress={() =>
                {
                    this._connectClient();
                }}>
                    <View style={styles.button}>
                        <Text style={{color: 'white'}}>连接</Text>
                    </View>
                </TouchableOpacity>

                <TouchableOpacity onPress={() =>
                {
                    this._publishClient();
                }}>
                    <View style={styles.button}>
                        <Text style={{color: 'white'}}>发送</Text>
                    </View>
                </TouchableOpacity>

            </View>
        )
    }

    componentWillMount()
    {
        BackAndroid.addEventListener('hardwareBackPress', () => this.onBackAndroid());
    }

    componentWillUnMount()
    {
        BackAndroid.removeEventListener('hardwareBackPress', () => this.onBackAndroid());
    }

    onBackAndroid()
    {
        const {navigator} = this.props;
        const routers = navigator.getCurrentRoutes();
        if (routers.length > 1)
        {
            navigator.pop();
            return true;
        }
        return false;
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
        alignItems: 'center'
    },
    toolbarStatus: {
        marginTop: statusHeight(),
        backgroundColor: 'white'
    },
    button: {
        width: 70,
        height: 70,
        backgroundColor: 'red',
        borderRadius: 50,
        justifyContent: 'center',
        alignItems: 'center',
        marginTop: 20
    }
});

export default MqttPage;<commit_msg>添加断开按钮<commit_after>'use strict';

import React, {Component} from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    Image,
    TouchableOpacity,
    TextInput,
    BackAndroid
} from 'react-native';
import mqtt from 'react-native-mqtt';
import {statusHeight} from '../common/CommonApi';
import {toastShort} from '../component/Toast';

var agentId = '577ba294280c474b40aad690';
var agentToken = 'WlCllVNCPCZlHlhVyxaNHBiwABdgwuMk';
var SUBSCRIBE_TOPIC = "agents/" + agentId + "/downstream";
var PUBLISH_TOPIC = "jsonUp";
var mqttClient;

class MqttPage extends Component {
    constructor(props)
    {
        super(props);

        var options = {
            host: 'broker.j1st.io',
            port: 1883,
            protocol: 'tcp',
            clientId: agentId,
            user: agentId,
            pass: agentToken,
            tls: false,
            auth: true
        };

        mqtt.createClient(options).then(function (client)
        {
            mqttClient = client;

            client.on('closed', function ()
            {
                console.log('mqtt.event.closed');
                toastShort('断开成功');
            });

            client.on('error', function (msg)
            {
                console.log('mqtt.event.error', msg);

            });

            client.on('message', function (msg)
            {
                console.log('mqtt.event.message', msg);
                toastShort('接收到消息' + JSON.stringify(msg));
            });

            client.on('connect', function ()
            {
                console.log('连接成功');
                client.subscribe(SUBSCRIBE_TOPIC, 0);
                toastShort('连接成功');
            });
        }).catch(function (err)
        {
            console.log(err);
        });
    }

    _connectClient()
    {
        mqttClient.connect();
    }

    _publishClient()
    {
        mqttClient.publish(SUBSCRIBE_TOPIC, 'test', 0, false);
    }

    _disconnectClient()
    {
        mqttClient.disconnect();
    }

    render()
    {
        return (
            <View style={styles.container}>
                <View style={styles.toolbarStatus}/>
                <Text style={{textAlign: 'center', fontSize: 30}}>Mqtt Sample</Text>
                <View style={{flexDirection: 'row', justifyContent: 'center', marginTop: 40}}>
                    <Text style={{flex: 2, textAlign: 'center'}}>Agent Id</Text>
                    <Text style={{flex: 5, textAlign: 'center'}}>{agentId}</Text>
                </View>
                <View style={{flexDirection: 'row', justifyContent: 'center', marginTop: 40}}>
                    <Text style={{flex: 2, textAlign: 'center'}}>Agent Token</Text>
                    <Text style={{flex: 5, textAlign: 'center'}}>{agentToken}</Text>
                </View>
                <TouchableOpacity onPress={() =>
                {
                    this._connectClient();
                }}>
                    <View style={styles.button}>
                        <Text style={{color: 'white'}}>连接</Text>
                    </View>
                </TouchableOpacity>

                <TouchableOpacity onPress={() =>
                {
                    this._publishClient();
                }}>
                    <View style={styles.button}>
                        <Text style={{color: 'white'}}>发送</Text>
                    </View>
                </TouchableOpacity>

                <TouchableOpacity onPress={() =>
                {
                    this._disconnectClient();
                }}>
                    <View style={styles.button}>
                        <Text style={{color: 'white'}}>断开</Text>
                    </View>
                </TouchableOpacity>

            </View>
        )
    }

    componentWillMount()
    {
        BackAndroid.addEventListener('hardwareBackPress', () => this.onBackAndroid());
    }

    componentWillUnMount()
    {
        BackAndroid.removeEventListener('hardwareBackPress', () => this.onBackAndroid());
    }

    onBackAndroid()
    {
        const {navigator} = this.props;
        const routers = navigator.getCurrentRoutes();
        if (routers.length > 1)
        {
            navigator.pop();
            return true;
        }
        return false;
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'white',
        alignItems: 'center'
    },
    toolbarStatus: {
        marginTop: statusHeight(),
        backgroundColor: 'white'
    },
    button: {
        width: 70,
        height: 70,
        backgroundColor: 'red',
        borderRadius: 50,
        justifyContent: 'center',
        alignItems: 'center',
        marginTop: 20
    }
});

export default MqttPage;<|endoftext|>


<commit_before>/*jshint camelcase: false*/
/*globals define*/
define([
  'box2d',
  'entities/entity',
  'geometry/geometry-factory',
  'utils',
  'world'
], function( Box2D, Entity, GeometryFactory, Utils, world ) {
  'use strict';

  var Vec2 = Box2D.Common.Math.b2Vec2;
  var Body = Box2D.Dynamics.b2Body;
  var BodyDef = Box2D.Dynamics.b2BodyDef;
  var FixtureDef = Box2D.Dynamics.b2FixtureDef;

  var shapeClasses = {
    circle: Box2D.Collision.Shapes.b2CircleShape,
    polygon: Box2D.Collision.Shapes.b2PolygonShape
  };

  var defaultShape = 'circle';

  var bodyTypes = {
    'static': Body.b2_staticBody,
    'dynamic': Body.b2_dynamicBody,
    'kinematic': Body.b2_kinematicBody
  };

  var setAs = {
    array: 'SetAsArray',
    box: 'SetAsBox',
    edge: 'SetAsEdge',
    orientedBox: 'SetAsOrientedBox',
    vector: 'SetAsVector'
  };

  var set = Utils.set;

  function PhysicsEntity( options ) {
    if ( !options ) {
      return;
    }

    this.fixture = null;
    this.initialize( options );

    // This tautology stops the Entity constructor from changing the position.
    Entity.call( this, this.x, this.y );

    // Add any shapes.
    if ( options.shapes ) {
      this.shapes = this.shapes.concat( options.shapes.map(function( shapeData ) {
        return GeometryFactory.create( JSON.stringify( shapeData ) );
      }));
    }
  }

  PhysicsEntity.prototype = new Entity();
  PhysicsEntity.prototype.constructor = PhysicsEntity;

  PhysicsEntity.prototype.initialize = function( options ) {
    options = options || {};

    var fixDef = new FixtureDef();
    set( fixDef, options.fixture );
    this.fixtureShape( fixDef, options );

    var bodyDef = new BodyDef();
    set( bodyDef, options.body );
    // Convert body type from string to flag.
    if ( options.body ) {
      bodyDef.type = typeof bodyDef.type !== 'undefined' ? bodyTypes[ bodyDef.type ] : Body.b2_staticBody;
    }

    this.fixture = world.CreateBody( bodyDef ).CreateFixture( fixDef );
    this.body.SetUserData( this );
  };

  /**
   * Creates a shape of the class given by the string shape called with a
   * shapeOptions array.
   *
   * Possible values for shape are:
   *  - circle (default)
   *  - polygon
   */
  PhysicsEntity.prototype.fixtureShape = function( fixDef, options ) {
    var shape = typeof options.shape !== 'undefined' ? options.shape : defaultShape;

    var Shape = shapeClasses[ shape ];
    if ( typeof Shape === 'undefined' ) {
      Shape = shapeClasses[ defaultShape ];
    }

    fixDef.shape = new Shape();

    // Set up shape properties.
    if ( shape === 'circle' ) {
      if ( typeof options.radius !== 'undefined' ) {
        fixDef.shape.SetRadius( options.radius );
      }

      return;
    }

    // Handle SetAs functions for each possible type.
    var type = options.type,
        data = options.data;

    if ( shape !== 'polygon' ||
         typeof type === 'undefined' ||
         typeof data === 'undefined' ) {
      return;
    }

    var setAsFunction = setAs[ type ];
    if ( typeof setAsFunction === 'undefined' ) {
      return;
    }

    setAsFunction = fixDef.shape[ setAsFunction ];

    // Data is an array.
    if ( type === 'array' ||
         type === 'vector' ) {
      // Convert flat array of numbers to a Vec2 array.
      var vector = [];
      for ( var i = 0, il = 0.5 * data.length; i < il; i++ ) {
        vector.push( new Vec2( data[ 2 * i ], data[ 2 * i + 1 ] ) );
      }

      setAsFunction.call( fixDef.shape, vector, vector.length );
    }

    // Data is an object:
    // - hx:Number
    // - hy:Number
    else if ( type === 'box' ) {
      setAsFunction.call( fixDef.shape, data.hx, data.hy );
    }

    // Data is an object:
    // - hx:Number
    // - hy:Number
    // - center:Vec2
    // - angle:Number
    else if ( type === 'orientedbox' ) {
      var center;
      if ( typeof data.center !== 'undefined' ) {
        center = new Vec2( data.center.x, data.center.y );
      } else {
        center = new Vec2( 0, 0 );
      }

      setAsFunction.call( fixDef.shape, data.hx, data.hy, center, data.angle );
    }

    // Data is an array of 4 numbers.
    else if ( type === 'edge' ) {
      setAsFunction.call(
        fixDef.shape,
        new Vec2( data[0], data[1] ),
        new Vec2( data[2], data[3] )
      );
    }
  };

  PhysicsEntity.prototype.accelerate = function( x, y ) {
    this.body.ApplyImpulse(
      new Vec2( x, y ),
      this.worldCenter
    );
  };

  PhysicsEntity.prototype.update = function( dt ) {
    Entity.prototype.update.call( this, dt );
    this.vx = Utils.roundNearZero( this.vx );
    this.vy = Utils.roundNearZero( this.vy );
  };

  Object.defineProperty( PhysicsEntity.prototype, 'body', {
    get: function() {
      return this.fixture.GetBody();
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'material', {
    get: function() {
      return this.fixture.GetFilterData().categoryBits;
    },

    set: function( material ) {
      var filterData = this.fixture.GetFilterData();
      filterData.categoryBits = material;
      this.fixture.SetFilterData( filterData );
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'position', {
    get: function() {
      return this.body.GetPosition();
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'worldCenter', {
    get: function() {
      return this.body.GetWorldCenter();
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'x', {
    enumerable: true,

    get: function() {
      return this.position.x;
    },

    set: function( x ) {
      this.position.x    = x || 0;
      this.worldCenter.x = x || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'y', {
    enumerable: true,

    get: function() {
      return this.position.y;
    },

    set: function( y ) {
      this.position.y    = y || 0;
      this.worldCenter.y = y || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'angle', {
    enumerable: true,

    get: function() {
      return this.body.GetAngle();
    },

    set: function( angle ) {
      this.body.SetAngle( angle || 0 );
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'velocity', {
    get: function() {
      return this.body.GetLinearVelocity();
    },

    set: function( velocity ) {
      this.body.SetLinearVelocity( velocity );
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'vx', {
    get: function() {
      return this.velocity.x;
    },

    set: function( vx ) {
      this.velocity.x = vx || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'vy', {
    get: function() {
      return this.velocity.y;
    },

    set: function( vy ) {
      this.velocity.y = vy || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'va', {
    get: function() {
      return this.body.GetAngularVelocity();
    },

    set: function( va ) {
      this.body.SetAngularVelocity( va || 0 );
    }
  });

  return PhysicsEntity;
});
<commit_msg>Fix angles in PhysicsEntity.<commit_after>/*jshint camelcase: false*/
/*globals define*/
define([
  'box2d',
  'entities/entity',
  'geometry/geometry-factory',
  'utils',
  'world'
], function( Box2D, Entity, GeometryFactory, Utils, world ) {
  'use strict';

  var Vec2 = Box2D.Common.Math.b2Vec2;
  var Body = Box2D.Dynamics.b2Body;
  var BodyDef = Box2D.Dynamics.b2BodyDef;
  var FixtureDef = Box2D.Dynamics.b2FixtureDef;

  var shapeClasses = {
    circle: Box2D.Collision.Shapes.b2CircleShape,
    polygon: Box2D.Collision.Shapes.b2PolygonShape
  };

  var defaultShape = 'circle';

  var bodyTypes = {
    'static': Body.b2_staticBody,
    'dynamic': Body.b2_dynamicBody,
    'kinematic': Body.b2_kinematicBody
  };

  var setAs = {
    array: 'SetAsArray',
    box: 'SetAsBox',
    edge: 'SetAsEdge',
    orientedBox: 'SetAsOrientedBox',
    vector: 'SetAsVector'
  };

  var set = Utils.set;

  function PhysicsEntity( options ) {
    if ( !options ) {
      return;
    }

    this.fixture = null;
    this.initialize( options );

    // This tautology stops the Entity constructor from changing the position/angle.
    var angle = this.angle;
    Entity.call( this, this.x, this.y );
    this.angle = -angle;

    // Add any shapes.
    if ( options.shapes ) {
      this.shapes = this.shapes.concat( options.shapes.map(function( shapeData ) {
        return GeometryFactory.create( JSON.stringify( shapeData ) );
      }));
    }
  }

  PhysicsEntity.prototype = new Entity();
  PhysicsEntity.prototype.constructor = PhysicsEntity;

  PhysicsEntity.prototype.initialize = function( options ) {
    options = options || {};

    var fixDef = new FixtureDef();
    set( fixDef, options.fixture );
    this.fixtureShape( fixDef, options );

    var bodyDef = new BodyDef();
    set( bodyDef, options.body );
    // Convert body type from string to flag.
    if ( options.body ) {
      bodyDef.type = typeof bodyDef.type !== 'undefined' ? bodyTypes[ bodyDef.type ] : Body.b2_staticBody;
    }

    this.fixture = world.CreateBody( bodyDef ).CreateFixture( fixDef );
    this.body.SetUserData( this );
  };

  /**
   * Creates a shape of the class given by the string shape called with a
   * shapeOptions array.
   *
   * Possible values for shape are:
   *  - circle (default)
   *  - polygon
   */
  PhysicsEntity.prototype.fixtureShape = function( fixDef, options ) {
    var shape = typeof options.shape !== 'undefined' ? options.shape : defaultShape;

    var Shape = shapeClasses[ shape ];
    if ( typeof Shape === 'undefined' ) {
      Shape = shapeClasses[ defaultShape ];
    }

    fixDef.shape = new Shape();

    // Set up shape properties.
    if ( shape === 'circle' ) {
      if ( typeof options.radius !== 'undefined' ) {
        fixDef.shape.SetRadius( options.radius );
      }

      return;
    }

    // Handle SetAs functions for each possible type.
    var type = options.type,
        data = options.data;

    if ( shape !== 'polygon' ||
         typeof type === 'undefined' ||
         typeof data === 'undefined' ) {
      return;
    }

    var setAsFunction = setAs[ type ];
    if ( typeof setAsFunction === 'undefined' ) {
      return;
    }

    setAsFunction = fixDef.shape[ setAsFunction ];

    // Data is an array.
    if ( type === 'array' ||
         type === 'vector' ) {
      // Convert flat array of numbers to a Vec2 array.
      var vector = [];
      for ( var i = 0, il = 0.5 * data.length; i < il; i++ ) {
        vector.push( new Vec2( data[ 2 * i ], data[ 2 * i + 1 ] ) );
      }

      setAsFunction.call( fixDef.shape, vector, vector.length );
    }

    // Data is an object:
    // - hx:Number
    // - hy:Number
    else if ( type === 'box' ) {
      setAsFunction.call( fixDef.shape, data.hx, data.hy );
    }

    // Data is an object:
    // - hx:Number
    // - hy:Number
    // - center:Vec2
    // - angle:Number
    else if ( type === 'orientedbox' ) {
      var center;
      if ( typeof data.center !== 'undefined' ) {
        center = new Vec2( data.center.x, data.center.y );
      } else {
        center = new Vec2( 0, 0 );
      }

      setAsFunction.call( fixDef.shape, data.hx, data.hy, center, data.angle );
    }

    // Data is an array of 4 numbers.
    else if ( type === 'edge' ) {
      setAsFunction.call(
        fixDef.shape,
        new Vec2( data[0], data[1] ),
        new Vec2( data[2], data[3] )
      );
    }
  };

  PhysicsEntity.prototype.accelerate = function( x, y ) {
    this.body.ApplyImpulse(
      new Vec2( x, y ),
      this.worldCenter
    );
  };

  PhysicsEntity.prototype.update = function( dt ) {
    Entity.prototype.update.call( this, dt );
    this.vx = Utils.roundNearZero( this.vx );
    this.vy = Utils.roundNearZero( this.vy );
  };

  Object.defineProperty( PhysicsEntity.prototype, 'body', {
    get: function() {
      return this.fixture.GetBody();
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'material', {
    get: function() {
      return this.fixture.GetFilterData().categoryBits;
    },

    set: function( material ) {
      var filterData = this.fixture.GetFilterData();
      filterData.categoryBits = material;
      this.fixture.SetFilterData( filterData );
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'position', {
    get: function() {
      return this.body.GetPosition();
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'worldCenter', {
    get: function() {
      return this.body.GetWorldCenter();
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'x', {
    enumerable: true,

    get: function() {
      return this.position.x;
    },

    set: function( x ) {
      this.position.x    = x || 0;
      this.worldCenter.x = x || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'y', {
    enumerable: true,

    get: function() {
      return this.position.y;
    },

    set: function( y ) {
      this.position.y    = y || 0;
      this.worldCenter.y = y || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'angle', {
    enumerable: true,

    get: function() {
      return -this.body.GetAngle();
    },

    set: function( angle ) {
      this.body.SetAngle( -angle || 0 );
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'velocity', {
    get: function() {
      return this.body.GetLinearVelocity();
    },

    set: function( velocity ) {
      this.body.SetLinearVelocity( velocity );
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'vx', {
    get: function() {
      return this.velocity.x;
    },

    set: function( vx ) {
      this.velocity.x = vx || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'vy', {
    get: function() {
      return this.velocity.y;
    },

    set: function( vy ) {
      this.velocity.y = vy || 0;
    }
  });

  Object.defineProperty( PhysicsEntity.prototype, 'va', {
    get: function() {
      return this.body.GetAngularVelocity();
    },

    set: function( va ) {
      this.body.SetAngularVelocity( va || 0 );
    }
  });

  return PhysicsEntity;
});
<|endoftext|>


<commit_before><commit_msg>added onCloseButton to Dialog<commit_after><|endoftext|>


<commit_before>let log = [];
let b = [];
let resolution = 50;
let n = 2;
let knots = [0, 0, 0, 1, 2, 3, 3, 3];
let pDef = [[-1, 1], [-0.5, -1], [0, 1], [0.5, -1], [1, 1]];
let p = [];
let tmin = 0;
let tmax = 3;

function setup() {
  canvas = createCanvas(windowWidth, windowHeight);

  for (let i = 0; i < pDef.length; i ++) {
    p.push(createVector(pDef[i][0], pDef[i][1]));
  }

  for (let i = 0; i < p.length; i ++) {
    b.push([]);
    for (let j = tmin; j <= tmax * resolution; j += 1) {
      let x = j / resolution;
      if (x >= tmax) { x = tmax - 0.00001; }
      b[i].push(bN(i, n, x));
    }
  }
}

function draw() {
  clear();
  background(15, 243, 208);

  let interval = 30;
  let len = b[0].length + interval;
  let f = frameCount - 1;
  if (f % len == 0) { log = []; }
  let idx = constrain(f % len - interval / 2, 0, b[0].length - 1);

  drawGraph(idx);
  drawCurve(idx);
}

function drawGraph(idx) {
  noFill(); stroke(0);
  let w = width * 0.6, h = height * 0.2;
  let x = (width - w) / 2, y = height - 32;

  push();
  translate(x, y);
  for (let i = 0; i < b.length; i ++) {
    beginShape();
    for (let j = 0; j < b[i].length; j ++) {
      let v = b[i][j];
      vertex(j / resolution * w / tmax, -v * h);
    }
    endShape();
  }
  line(idx / resolution * w / tmax, 0, idx / resolution * w / tmax, -h);

  fill(0);
  for (let i = 0; i < b.length; i ++) {
    let v = b[i][idx];
    drawCircleMarker(createVector(idx / resolution * w / tmax, -v * h), 2);
  }

  for (let i = 0; i <= tmax; i ++) {
    drawCircleMarker(createVector(i * w / tmax, 0), 2);

    drawLabel(i * w / tmax, 16, "x = " + i);
  }

  drawLabel(0, -h - 8, "B i,n(t)");
  pop();
}

function drawCurve(idx) {
  fill(0); stroke(0);
  let s = createVector(width * 0.4, height * 0.2); // scale

  push();
  translate(width / 2, height / 3 * 1);

  for (let i = 0; i < p.length; i ++) {
    drawCircleMarker(createVector(p[i].x * s.x, p[i].y * s.y), 3);
    drawLabel(p[i].x * s.x, p[i].y * s.y + 24, "P" + i);
    if (i > 0) {
      line(p[i - 1].x * s.x, p[i - 1].y * s.y, p[i].x * s.x, p[i].y * s.y);
    }
  }

  let pt = createVector();
  let v = [];
  for (let i = 0; i < p.length; i ++) {
    v.push(b[i][idx]);
    pt.add(p[i].copy().mult(v[i]))
  }

  drawCircleMarker(createVector(pt.x * s.x, pt.y * s.y), 4);
  log.push(pt);
  for (let i = 0; i < p.length; i ++) {
    stroke(0, 0, 0, v[i] * 255);
    line(pt.x * s.x, pt.y * s.y, p[i].x * s.x, p[i].y * s.y);
  }

  noFill(); stroke(1); strokeWeight(2);
  beginShape();
  for (let i = 0; i < log.length; i ++) {
    vertex(log[i].x * s.x, log[i].y * s.y);
  }
  endShape();

  pop();
}

function bN(i, k, x){
  let w1 = 0, w2 = 0;
  if(k == 0){
    if (x >= knots[i] && x < knots[i + 1]) {
      return 1;
    } else {
      return 0;
    }
  } else {
    if ((knots[i + k] - knots[i]) != 0 ){
      w1 = ((x - knots[i]) / (knots[i + k] - knots[i])) * bN(i, k - 1, x);
    }

    if ((knots[i + k + 1] - knots[i + 1]) != 0) {
      w2 = ((knots[i + k + 1] - x) / (knots[i + k + 1] - knots[i + 1])) * bN(i + 1, k - 1, x);
    }
    return w1 + w2;
  }
}

/** drawLabel **/

/** drawCircleMarker **/
<commit_msg>chang t to x<commit_after>let log = [];
let b = [];
let resolution = 50;
let n = 2;
let knots = [0, 0, 0, 1, 2, 3, 3, 3];
let pDef = [[-1, 1], [-0.5, -1], [0, 1], [0.5, -1], [1, 1]];
let p = [];
let tmin = 0;
let tmax = 3;

function setup() {
  canvas = createCanvas(windowWidth, windowHeight);

  for (let i = 0; i < pDef.length; i ++) {
    p.push(createVector(pDef[i][0], pDef[i][1]));
  }

  for (let i = 0; i < p.length; i ++) {
    b.push([]);
    for (let j = tmin; j <= tmax * resolution; j += 1) {
      let x = j / resolution;
      if (x >= tmax) { x = tmax - 0.00001; }
      b[i].push(bN(i, n, x));
    }
  }
}

function draw() {
  clear();
  background(15, 243, 208);

  let interval = 30;
  let len = b[0].length + interval;
  let f = frameCount - 1;
  if (f % len == 0) { log = []; }
  let idx = constrain(f % len - interval / 2, 0, b[0].length - 1);

  drawGraph(idx);
  drawCurve(idx);
}

function drawGraph(idx) {
  noFill(); stroke(0);
  let w = width * 0.6, h = height * 0.2;
  let x = (width - w) / 2, y = height - 32;

  push();
  translate(x, y);
  for (let i = 0; i < b.length; i ++) {
    beginShape();
    for (let j = 0; j < b[i].length; j ++) {
      let v = b[i][j];
      vertex(j / resolution * w / tmax, -v * h);
    }
    endShape();
  }
  line(idx / resolution * w / tmax, 0, idx / resolution * w / tmax, -h);

  fill(0);
  for (let i = 0; i < b.length; i ++) {
    let v = b[i][idx];
    drawCircleMarker(createVector(idx / resolution * w / tmax, -v * h), 2);
  }

  for (let i = 0; i <= tmax; i ++) {
    drawCircleMarker(createVector(i * w / tmax, 0), 2);

    drawLabel(i * w / tmax, 16, "x = " + i);
  }

  drawLabel(0, -h - 8, "B i,n(x)");
  pop();
}

function drawCurve(idx) {
  fill(0); stroke(0);
  let s = createVector(width * 0.4, height * 0.2); // scale

  push();
  translate(width / 2, height / 3 * 1);

  for (let i = 0; i < p.length; i ++) {
    drawCircleMarker(createVector(p[i].x * s.x, p[i].y * s.y), 3);
    drawLabel(p[i].x * s.x, p[i].y * s.y + 24, "P" + i);
    if (i > 0) {
      line(p[i - 1].x * s.x, p[i - 1].y * s.y, p[i].x * s.x, p[i].y * s.y);
    }
  }

  let pt = createVector();
  let v = [];
  for (let i = 0; i < p.length; i ++) {
    v.push(b[i][idx]);
    pt.add(p[i].copy().mult(v[i]))
  }

  drawCircleMarker(createVector(pt.x * s.x, pt.y * s.y), 4);
  log.push(pt);
  for (let i = 0; i < p.length; i ++) {
    stroke(0, 0, 0, v[i] * 255);
    line(pt.x * s.x, pt.y * s.y, p[i].x * s.x, p[i].y * s.y);
  }

  noFill(); stroke(1); strokeWeight(2);
  beginShape();
  for (let i = 0; i < log.length; i ++) {
    vertex(log[i].x * s.x, log[i].y * s.y);
  }
  endShape();

  pop();
}

function bN(i, k, x){
  let w1 = 0, w2 = 0;
  if(k == 0){
    if (x >= knots[i] && x < knots[i + 1]) {
      return 1;
    } else {
      return 0;
    }
  } else {
    if ((knots[i + k] - knots[i]) != 0 ){
      w1 = ((x - knots[i]) / (knots[i + k] - knots[i])) * bN(i, k - 1, x);
    }

    if ((knots[i + k + 1] - knots[i + 1]) != 0) {
      w2 = ((knots[i + k + 1] - x) / (knots[i + k + 1] - knots[i + 1])) * bN(i + 1, k - 1, x);
    }
    return w1 + w2;
  }
}

/** drawLabel **/

/** drawCircleMarker **/
<|endoftext|>


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

/**
 * RequireJS configuration file for the 'Fraction Matcher' sim.
 * Paths are relative to the location of this file.
 *
 * @author Anton Ulyanov (Mlearner)
 */

require.config( {

  deps: [ 'fraction-matcher-main' ],

  config: {
    i18n: {
      locale: 'en_us'
    }
  },

  paths: {

    // third-party libs
    text: '../../sherpa/lib/text-2.0.12',

    // PhET plugins
    audio: '../../chipper/js/requirejs-plugins/audio',
    image: '../../chipper/js/requirejs-plugins/image',
    mipmap: '../../chipper/js/requirejs-plugins/mipmap',
    string: '../../chipper/js/requirejs-plugins/string',
    ifphetio: '../../chipper/js/requirejs-plugins/ifphetio',

    // common directories, uppercase names to identify them in require imports
    AXON: '../../axon/js',
    BRAND: '../../brand/' + phet.chipper.brand + '/js',
    DOT: '../../dot/js',
    JOIST: '../../joist/js',
    KITE: '../../kite/js',
    PHET_CORE: '../../phet-core/js',
    PHET_IO: '../../phet-io/js',
    PHETCOMMON: '../../phetcommon/js',
    REPOSITORY: '..',
    SCENERY: '../../scenery/js',
    SCENERY_PHET: '../../scenery-phet/js',
    SUN: '../../sun/js',
    TANDEM: '../../tandem/js',
    VIBE: '../../vibe/js',
    VEGAS: '../../vegas/js',

    //Sim code
    FRACTION_MATCHER: '.'
  },

  // optional cache buster to make browser refresh load all included scripts, can be disabled with ?cacheBuster=false
  urlArgs: phet.chipper.getCacheBusterArgs()
} );
<commit_msg>Generated config file, see https://github.com/phetsims/chipper/issues/580<commit_after>// Copyright 2017, University of Colorado Boulder

/*
 * IMPORTANT: This file was auto-generated by "grunt generate-config". Please do not modify this directly. Instead
 * please modify fraction-matcher/package.json to control dependencies.
 *
 * RequireJS configuration file for the fraction-matcher sim.
 * Paths are relative to the location of this file.
 */

require.config( {

  deps: [ 'fraction-matcher-main' ],

  paths: {

    // Third-party libs
    text: '../../sherpa/lib/text-2.0.12',

    // PhET plugins
    audio: '../../chipper/js/requirejs-plugins/audio',
    image: '../../chipper/js/requirejs-plugins/image',
    mipmap: '../../chipper/js/requirejs-plugins/mipmap',
    string: '../../chipper/js/requirejs-plugins/string',
    ifphetio: '../../chipper/js/requirejs-plugins/ifphetio',

    // PhET libs, uppercase names to identify them in require.js imports.
    // IMPORTANT: DO NOT modify. This file is auto-generated. See documentation at the top.
    AXON: '../../axon/js',
    BRAND: '../../brand/' + phet.chipper.brand + '/js',
    DOT: '../../dot/js',
    FRACTION_MATCHER: '.',
    JOIST: '../../joist/js',
    KITE: '../../kite/js',
    PHETCOMMON: '../../phetcommon/js',
    PHET_CORE: '../../phet-core/js',
    PHET_IO: '../../phet-io/js',
    REPOSITORY: '..',
    SCENERY: '../../scenery/js',
    SCENERY_PHET: '../../scenery-phet/js',
    SUN: '../../sun/js',
    TANDEM: '../../tandem/js',
    VEGAS: '../../vegas/js',
    VIBE: '../../vibe/js'
  },

  // optional cache buster to make browser refresh load all included scripts, can be disabled with ?cacheBuster=false
  urlArgs: phet.chipper.getCacheBusterArgs()
} );
<|endoftext|>


<commit_before>(function ($) {
    $.mobiscroll.i18n.nl = $.extend($.mobiscroll.i18n.nl, {
        // Core
        setText: 'Instellen',
        cancelText: 'Annuleer',
        // Datetime component
        dateFormat: 'dd/mm/yy',
        dateOrder: 'ddmmyy',
        dayNames: ['zondag', 'maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
        dayNamesShort: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
        dayText: 'Dag',
        hourText: 'Uur',
        minuteText: 'Minuten',
        monthNames: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
        monthNamesShort: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
        monthText: 'Maand',
        secText: 'Seconden',
        timeFormat: 'hh:ii A',
        timeWheels: 'hhiiA',
        yearText: 'Jaar',
        nowText: 'Nu',
        // Calendar component
        dateText: 'Datum',
        timeText: 'Tijd',
        calendarText: 'Kalender',
        closeText: 'Sluiten',
        // Daterange component
        fromText: 'Start',
        toText: 'End',
        // Measurement components
        wholeText: 'geheel',
        fractionText: 'fractie',
        unitText: 'eenheid',
        // Time / Timespan component
        labels: ['Jaren', 'Maanden', 'Dagen', 'Uren', 'Minuten', 'Seconden', ''],
        labelsShort: ['j', 'm', 'd', 'u', 'min', 'sec', ''],
        // Timer component
        startText: 'Start',
        stopText: 'Stop',
        resetText: 'Reset',
        lapText: 'Lap',
        hideText: 'Verbergen'
    });
})(jQuery);
<commit_msg>improved dutch translation<commit_after>(function ($) {
    $.mobiscroll.i18n.nl = $.extend($.mobiscroll.i18n.nl, {
        // Core
        setText: 'Instellen',
        cancelText: 'Annuleren',
        // Datetime component
        dateFormat: 'dd/mm/yy',
        dateOrder: 'ddmmyy',
        dayNames: ['zondag', 'maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
        dayNamesShort: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
        dayText: 'Dag',
        hourText: 'Uur',
        minuteText: 'Minuten',
        monthNames: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'],
        monthNamesShort: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'],
        monthText: 'Maand',
        secText: 'Seconden',
        timeFormat: 'HH:ii',
        timeWheels: 'hhii',
        yearText: 'Jaar',
        nowText: 'Nu',
        // Calendar component
        dateText: 'Datum',
        timeText: 'Tijd',
        calendarText: 'Kalender',
        closeText: 'Sluiten',
        // Daterange component
        fromText: 'Start',
        toText: 'Einde',
        // Measurement components
        wholeText: 'geheel',
        fractionText: 'fractie',
        unitText: 'eenheid',
        // Time / Timespan component
        labels: ['Jaren', 'Maanden', 'Dagen', 'Uren', 'Minuten', 'Seconden', ''],
        labelsShort: ['j', 'm', 'd', 'u', 'min', 'sec', ''],
        // Timer component
        startText: 'Start',
        stopText: 'Stop',
        resetText: 'Reset',
        lapText: 'Ronde',
        hideText: 'Verbergen'
    });
})(jQuery);
<|endoftext|>


<commit_before>// Copyright 2002-2012, University of Colorado

/**
 * Basic dragging for a node.
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

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

  var assert = require( 'ASSERT/assert' )( 'scenery' );

  var Matrix3 = require( 'DOT/Matrix3' );

  var scenery = require( 'SCENERY/scenery' );

  /*
   * Allowed options: {
   *    allowTouchSnag: false // allow touch swipes across an object to pick it up,
   *    start: null           // if non-null, called when a drag is started. start( finger, trail, event )
   *    drag: null            // if non-null, called when the user moves something with a drag (not a start or end event).
   *                                                                         drag( finger, trail, event )
   *    end: null             // if non-null, called when a drag is ended.   end( finger, trail, event )
   *    translate:            // if this exists, translate( { delta: _, oldPosition: _, newPosition: _ } ) will be called instead of directly translating the node
   * }
   */
  scenery.SimpleDragHandler = function( options ) {
    var handler = this;

    this.options = _.extend( {
      allowTouchSnag: false,
    }, options );

    console.log( options );

    this.dragging              = false;     // whether a node is being dragged with this handler
    this.finger                = null;      // the finger doing the current dragging
    this.trail                 = null;      // stores the path to the node that is being dragged
    this.transform             = null;      // transform of the trail to our node (but not including our node, so we can prepend the deltas)
    this.node                  = null;      // the node that we are handling the drag for
    this.lastDragPoint         = null;      // the location of the drag at the previous event (so we can calculate a delta)
    this.startTransformMatrix  = null;      // the node's transform at the start of the drag, so we can reset on a touch cancel
    this.mouseButton           = undefined; // tracks which mouse button was pressed, so we can handle that specifically
    // TODO: consider mouse buttons as separate fingers?

    // if an ancestor is transformed, pin our node
    this.transformListener = {
      transform: function( args ) {
        if ( !handler.trail.isExtensionOf( args.trail, true ) ) {
          return;
        }

        var newMatrix = args.trail.getTransform().getMatrix();
        var oldMatrix = handler.transform.getMatrix();

        // if A was the trail's old transform, B is the trail's new transform, we need to apply (B^-1 A) to our node
        handler.node.prependMatrix( newMatrix.inverted().timesMatrix( oldMatrix ) );

        // store the new matrix so we can do deltas using it now
        handler.transform.set( newMatrix );
      }
    };

    // this listener gets added to the finger when it starts dragging our node
    this.dragListener = {
      // mouse/touch up
      up: function( event ) {
        assert && assert( event.finger === handler.finger );
        if ( !event.finger.isMouse || event.domEvent.button === handler.mouseButton ) {
          handler.endDrag( event );
        }
      },

      // touch cancel
      cancel: function( event ) {
        assert && assert( event.finger === handler.finger );
        handler.endDrag( event );

        // since it's a cancel event, go back!
        handler.node.setMatrix( handler.startTransformMatrix );
      },

      // mouse/touch move
      move: function( event ) {
        assert && assert( event.finger === handler.finger );

        var delta = handler.transform.inverseDelta2( handler.finger.point.minus( handler.lastDragPoint ) );

        // move by the delta between the previous point, using the precomputed transform
        // prepend the translation on the node, so we can ignore whatever other transform state the node has
        if ( handler.options.translate ) {
          var translation = handler.node.transform.getMatrix().translation();
          handler.options.translate( {
            delta: delta,
            oldPosition: translation,
            newPosition: translation.plus( delta )
          } );
        } else {
          handler.node.translate( delta, true );
        }
        handler.lastDragPoint = handler.finger.point;

        if ( handler.options.drag ) {
          // TODO: consider adding in a delta to the listener
          // TODO: add the position in to the listener
          handler.options.drag( handler.finger, handler.trail, event ); // new position (old position?) delta
        }
      }
    };
  };
  var SimpleDragHandler = scenery.SimpleDragHandler;

  SimpleDragHandler.prototype = {
    constructor: SimpleDragHandler,

    startDrag: function( event ) {
      // set a flag on the finger so it won't pick up other nodes
      event.finger.dragging = true;
      event.finger.addInputListener( this.dragListener );
      event.trail.rootNode().addEventListener( this.transformListener );

      // set all of our persistent information
      this.dragging = true;
      this.finger = event.finger;
      this.trail = event.trail.subtrailTo( event.currentTarget, true );
      this.transform = this.trail.getTransform();
      this.node = event.currentTarget;
      this.lastDragPoint = event.finger.point;
      this.startTransformMatrix = event.currentTarget.getMatrix();
      this.mouseButton = event.domEvent.button; // should be undefined for touch events

      if ( this.options.start ) {
        this.options.start( event.finger, this.trail, event );
      }
    },

    endDrag: function( event ) {
      this.finger.dragging = false;
      this.finger.removeInputListener( this.dragListener );
      this.trail.rootNode().removeEventListener( this.transformListener );
      this.dragging = false;

      if ( this.options.end ) {
        this.options.end( event );
      }
    },

    tryToSnag: function( event ) {
      // only start dragging if the finger isn't dragging anything, we aren't being dragged, and if it's a mouse it's button is down
      if ( !this.dragging && !event.finger.dragging ) {
        this.startDrag( event );
      }
    },

    /*---------------------------------------------------------------------------*
    * events called from the node input listener
    *----------------------------------------------------------------------------*/

    // mouse/touch down on this node
    down: function( event ) {
      this.tryToSnag( event );
    },

    // touch enters this node
    touchenter: function( event ) {
      // allow touches to start a drag by moving "over" this node
      if ( this.options.allowTouchSnag ) {
        this.tryToSnag( event );
      }
    }
  };

  return SimpleDragHandler;
} );

<commit_msg>Removing console.log<commit_after>// Copyright 2002-2012, University of Colorado

/**
 * Basic dragging for a node.
 *
 * @author Jonathan Olson <olsonsjc@gmail.com>
 */

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

  var assert = require( 'ASSERT/assert' )( 'scenery' );

  var Matrix3 = require( 'DOT/Matrix3' );

  var scenery = require( 'SCENERY/scenery' );

  /*
   * Allowed options: {
   *    allowTouchSnag: false // allow touch swipes across an object to pick it up,
   *    start: null           // if non-null, called when a drag is started. start( finger, trail, event )
   *    drag: null            // if non-null, called when the user moves something with a drag (not a start or end event).
   *                                                                         drag( finger, trail, event )
   *    end: null             // if non-null, called when a drag is ended.   end( finger, trail, event )
   *    translate:            // if this exists, translate( { delta: _, oldPosition: _, newPosition: _ } ) will be called instead of directly translating the node
   * }
   */
  scenery.SimpleDragHandler = function( options ) {
    var handler = this;

    this.options = _.extend( {
      allowTouchSnag: false,
    }, options );

    this.dragging              = false;     // whether a node is being dragged with this handler
    this.finger                = null;      // the finger doing the current dragging
    this.trail                 = null;      // stores the path to the node that is being dragged
    this.transform             = null;      // transform of the trail to our node (but not including our node, so we can prepend the deltas)
    this.node                  = null;      // the node that we are handling the drag for
    this.lastDragPoint         = null;      // the location of the drag at the previous event (so we can calculate a delta)
    this.startTransformMatrix  = null;      // the node's transform at the start of the drag, so we can reset on a touch cancel
    this.mouseButton           = undefined; // tracks which mouse button was pressed, so we can handle that specifically
    // TODO: consider mouse buttons as separate fingers?

    // if an ancestor is transformed, pin our node
    this.transformListener = {
      transform: function( args ) {
        if ( !handler.trail.isExtensionOf( args.trail, true ) ) {
          return;
        }

        var newMatrix = args.trail.getTransform().getMatrix();
        var oldMatrix = handler.transform.getMatrix();

        // if A was the trail's old transform, B is the trail's new transform, we need to apply (B^-1 A) to our node
        handler.node.prependMatrix( newMatrix.inverted().timesMatrix( oldMatrix ) );

        // store the new matrix so we can do deltas using it now
        handler.transform.set( newMatrix );
      }
    };

    // this listener gets added to the finger when it starts dragging our node
    this.dragListener = {
      // mouse/touch up
      up: function( event ) {
        assert && assert( event.finger === handler.finger );
        if ( !event.finger.isMouse || event.domEvent.button === handler.mouseButton ) {
          handler.endDrag( event );
        }
      },

      // touch cancel
      cancel: function( event ) {
        assert && assert( event.finger === handler.finger );
        handler.endDrag( event );

        // since it's a cancel event, go back!
        handler.node.setMatrix( handler.startTransformMatrix );
      },

      // mouse/touch move
      move: function( event ) {
        assert && assert( event.finger === handler.finger );

        var delta = handler.transform.inverseDelta2( handler.finger.point.minus( handler.lastDragPoint ) );

        // move by the delta between the previous point, using the precomputed transform
        // prepend the translation on the node, so we can ignore whatever other transform state the node has
        if ( handler.options.translate ) {
          var translation = handler.node.transform.getMatrix().translation();
          handler.options.translate( {
            delta: delta,
            oldPosition: translation,
            newPosition: translation.plus( delta )
          } );
        } else {
          handler.node.translate( delta, true );
        }
        handler.lastDragPoint = handler.finger.point;

        if ( handler.options.drag ) {
          // TODO: consider adding in a delta to the listener
          // TODO: add the position in to the listener
          handler.options.drag( handler.finger, handler.trail, event ); // new position (old position?) delta
        }
      }
    };
  };
  var SimpleDragHandler = scenery.SimpleDragHandler;

  SimpleDragHandler.prototype = {
    constructor: SimpleDragHandler,

    startDrag: function( event ) {
      // set a flag on the finger so it won't pick up other nodes
      event.finger.dragging = true;
      event.finger.addInputListener( this.dragListener );
      event.trail.rootNode().addEventListener( this.transformListener );

      // set all of our persistent information
      this.dragging = true;
      this.finger = event.finger;
      this.trail = event.trail.subtrailTo( event.currentTarget, true );
      this.transform = this.trail.getTransform();
      this.node = event.currentTarget;
      this.lastDragPoint = event.finger.point;
      this.startTransformMatrix = event.currentTarget.getMatrix();
      this.mouseButton = event.domEvent.button; // should be undefined for touch events

      if ( this.options.start ) {
        this.options.start( event.finger, this.trail, event );
      }
    },

    endDrag: function( event ) {
      this.finger.dragging = false;
      this.finger.removeInputListener( this.dragListener );
      this.trail.rootNode().removeEventListener( this.transformListener );
      this.dragging = false;

      if ( this.options.end ) {
        this.options.end( event );
      }
    },

    tryToSnag: function( event ) {
      // only start dragging if the finger isn't dragging anything, we aren't being dragged, and if it's a mouse it's button is down
      if ( !this.dragging && !event.finger.dragging ) {
        this.startDrag( event );
      }
    },

    /*---------------------------------------------------------------------------*
    * events called from the node input listener
    *----------------------------------------------------------------------------*/

    // mouse/touch down on this node
    down: function( event ) {
      this.tryToSnag( event );
    },

    // touch enters this node
    touchenter: function( event ) {
      // allow touches to start a drag by moving "over" this node
      if ( this.options.allowTouchSnag ) {
        this.tryToSnag( event );
      }
    }
  };

  return SimpleDragHandler;
} );

<|endoftext|>