<commit_before>;(function() {
    function Tablesort(el, options) {
        if (!el) throw new Error('Element not found');
        if (el.tagName !== 'TABLE') throw new Error('Element must be a table');
        this.init(el, options || {});
    }

    Tablesort.prototype = {

        init: function(el, options) {
            var that = this,
                firstRow;
            this.thead = false;
            this.options = options;

            if (el.rows && el.rows.length > 0) {
                if (el.tHead && el.tHead.rows.length > 0) {
                    firstRow = el.tHead.rows[el.tHead.rows.length - 1];
                    that.thead = true;
                } else {
                    firstRow = el.rows[0];
                }
            }

            if (!firstRow) return;

            var onClick = function() {
                if (that.current && that.current !== this) {
                    if (that.current.classList.contains(classSortUp)) {
                        that.current.classList.remove(classSortUp);
                    }
                    else if (that.current.classList.contains(classSortDown)) {
                        that.current.classList.remove(classSortDown);
                    }
                }

                that.current = this;
                that.sortTable(this);
            };

            var defaultSort;

            // Assume first row is the header and attach a click handler to each.
            for (var i = 0; i < firstRow.cells.length; i++) {
                var cell = firstRow.cells[i];
                if (!cell.classList.contains('no-sort')) {
                    cell.classList.add('sort-header');
                    cell.addEventListener('click', onClick, false);

                    if (cell.classList.contains('sort-default')) {
                        defaultSort = cell;
                    }
                }
            }

            if (defaultSort) {
                that.current = defaultSort;
                that.sortTable(defaultSort, true);
            }
        },

        getFirstDataRowIndex: function() {
            // If table does not have a <thead>, assume that first row is
            // a header and skip it.
            if (!this.thead) {
                return 1;
            } else {
                return 0;
            }
        },

        sortTable: function(header, update) {
            var that = this,
                column = header.cellIndex,
                sortFunction,
                t = getParent(header, 'table'),
                item = '',
                i = that.getFirstDataRowIndex();

            if (t.rows.length <= 1) return;

            while (item === '' && i < t.tBodies[0].rows.length) {
                item = getInnerText(t.tBodies[0].rows[i].cells[column]);
                item = item.trim();
                // Exclude cell values where commented out HTML exists
                if (item.substr(0, 4) === '<!--' || item.length === 0) {
                    item = '';
                }
                i++;
            }

            if (item === '') return;

            // Possible sortFunction scenarios
            var sortCaseInsensitive = function(a, b) {
                var aa = getInnerText(a.cells[that.col]).toLowerCase(),
                    bb = getInnerText(b.cells[that.col]).toLowerCase();

                if (aa === bb) return 0;
                if (aa < bb) return 1;

                return -1;
            };

            var sortNumber = function(a, b) {
                var aa = getInnerText(a.cells[that.col]),
                    bb = getInnerText(b.cells[that.col]);

                aa = cleanNumber(aa);
                bb = cleanNumber(bb);
                return compareNumber(bb, aa);
            };

            var sortDate = function(a, b) {
                var aa = getInnerText(a.cells[that.col]).toLowerCase(),
                    bb = getInnerText(b.cells[that.col]).toLowerCase();
                return parseDate(bb) - parseDate(aa);
            };

            var sortDotSep = function(a, b) {
                var aa = getInnerText(a.cells[that.col]).split('.'),
                    bb = getInnerText(b.cells[that.col]).split('.');

                for (var i = 0, len = aa.length; i < len; i++) {
                    var aai = parseInt(aa[i]),
                        bbi = parseInt(bb[i]);

                    if (aai == bbi) continue;
                    if (aai < bbi) return -1;
                    if (aai > bbi) return 1;
                }
                return 0;
            };

            var sortFilesize = function(a, b) {
                var aa = filesize2num(getInnerText(a.cells[that.col]))
                    bb = filesize2num(getInnerText(b.cells[that.col]));

                return compareNumber(bb, aa);
            };

            // Sort dot separted numbers, e.g. ip addresses or version numbers
            if (/^(\d+\.)+\d+$/.test(item)) {
                sortFunction = sortDotSep;
            }
            // sort filesize, e.g. "123.45 MB"
            else if (/^\d+(\.\d+)? ?(k|M|G|T)?i?B?$/i.test(item)) {
                sortFunction = sortFilesize;
            }
            // Sort as number if a currency key exists or number
            else if (item.match(/^-?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/) || // prefixed currency
                item.match(/^-?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/) || // suffixed currency
                item.match(/^-?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/) // number
            ) {
                sortFunction = sortNumber;
            } else if (testDate(item)) {
                sortFunction = sortDate;
            } else {
                sortFunction = sortCaseInsensitive;
            }

            this.col = column;
            var newRows = [],
                noSorts = {},
                j,
                totalRows = 0;

            for (i = 0; i < t.tBodies.length; i++) {
                for (j = 0; j < t.tBodies[i].rows.length; j++) {
                    var tr = t.tBodies[i].rows[j];
                    if (tr.classList.contains('no-sort')) {
                        // keep no-sorts in separate list to be able to insert
                        // them back at their original position later
                        noSorts[totalRows] = tr;
                    } else {
                        // Save the index for stable sorting
                        newRows.push({
                            tr: tr,
                            index: totalRows
                        });
                    }
                    totalRows++;
                }
            }

            var sortUp   = that.options.descending ? classSortDown : classSortUp,
                sortDown = that.options.descending ? classSortUp : classSortDown;

            if (!update) {
                if (header.classList.contains(sortUp)) {
                    header.classList.remove(sortUp);
                    header.classList.add(sortDown);
                } else {
                    header.classList.remove(sortDown);
                    header.classList.add(sortUp);
                }
            } else if (!header.classList.contains(sortUp) && !header.classList.contains(sortDown)) {
                header.classList.add(sortUp);
            }

            // Make a stable sort function
            var stabilize = function(sort) {
                return function(a, b) {
                    var unstableResult = sort(a.tr, b.tr);
                    if (unstableResult === 0) {
                        return a.index - b.index;
                    }
                    return unstableResult;
                };
            };

            // Make an `anti-stable` sort function. If two elements are equal
            // under the original sort function, then there relative order is
            // reversed.
            var antiStabilize = function(sort) {
                return function(a, b) {
                    var unstableResult = sort(a.tr, b.tr);
                    if (unstableResult === 0) {
                        return b.index - a.index;
                    }
                    return unstableResult;
                };
            };

            // Before we append should we reverse the new array or not?
            // If we reverse, the sort needs to be `anti-stable` so that
            // the double negatives cancel out
            if (header.classList.contains(classSortDown)) {
                newRows.sort(antiStabilize(sortFunction));
                newRows.reverse();
            } else {
                newRows.sort(stabilize(sortFunction));
            }

            // append rows that already exist rather than creating new ones
            var noSortsSoFar = 0;
            for (i = 0; i < totalRows; i++) {
                var whatToInsert;
                if (noSorts[i]) {
                    // We have a no-sort row for this position, insert it here.
                    whatToInsert = noSorts[i];
                    noSortsSoFar++;
                } else {
                    whatToInsert = newRows[i - noSortsSoFar].tr;
                }
                // appendChild(x) moves x if already present somewhere else in the DOM
                t.tBodies[0].appendChild(whatToInsert);
            }
        },

        refresh: function() {
            if (this.current !== undefined) {
                this.sortTable(this.current, true);
            }
        }
    };

    var classSortUp   = 'sort-up',
        classSortDown = 'sort-down';

    var week       = /(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\.?\,?\s*/i,
        commonDate = /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/,
        month      = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;

    var testDate = function(date) {
            return (
                date.search(week) !== -1 ||
                date.search(commonDate) !== -1  ||
                date.search(month !== -1)
            ) !== -1 && !isNaN(parseDate(date));
        },

        parseDate = function(date) {
            date = date.replace(/\-/g, '/');
            date = date.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, '$1/$2/$3'); // format before getTime
            return new Date(date).getTime();
        },

        getParent = function(el, pTagName) {
            if (el === null) {
                return null;
            } else if (el.nodeType === 1 && el.tagName.toLowerCase() === pTagName.toLowerCase()) {
                return el;
            } else {
                return getParent(el.parentNode, pTagName);
            }
        },

        getInnerText = function(el) {
            var that = this;

            if (typeof el === 'string' || typeof el === 'undefined') return el;

            var str = el.getAttribute('data-sort') || '';

            if (str) {
                return str;
            } else if (el.textContent) {
                return el.textContent;
            } else if (el.innerText) {
                return el.innerText;
            }

            var cs = el.childNodes,
                l = cs.length;

            for (var i = 0; i < l; i++) {
                switch (cs[i].nodeType) {
                    case 1:
                        // ELEMENT_NODE
                        str += that.getInnerText(cs[i]);
                    break;
                    case 3:
                        // TEXT_NODE
                        str += cs[i].nodeValue;
                    break;
                }
            }

            return str;
        },

        compareNumber = function(a, b) {
            var aa = parseFloat(a),
                bb = parseFloat(b);

            a = isNaN(aa) ? 0 : aa;
            b = isNaN(bb) ? 0 : bb;
            return a - b;
        },

        cleanNumber = function(i) {
            return i.replace(/[^\-?0-9.]/g, '');
        };

        // Converts filesize to bytes
        // Ex. filesize2num("123 KB") -> 123000
        // Ex. filesize2num("123 KiB") -> 125952
        filesize2num = function(filesize) {
            var matches = filesize.match(/^(\d+(\.\d+)?) ?((k|M|G|T)?i?B?)$/i);

            var num    = parseFloat(cleanNumber(matches[1])),
                suffix = matches[3];

            return num * suffix2num(suffix);
        };

        // Returns suffix multiplier
        // Ex. suffix2num("KB") -> 1000
        // Ex. suffix2num("KiB") -> 1024
        suffix2num = function(suffix) {
            suffix = suffix.toLowerCase();
            var base = suffix[1] === "i" ? 1024 : 1000;

            switch(suffix[0]) {
                case "k":
                    return Math.pow(base, 2);
                case "m":
                    return Math.pow(base, 3);
                case "g":
                    return Math.pow(base, 4);
                case "t":
                    return Math.pow(base, 5);
                default:
                    return base;
            }
        };

    if (typeof module !== 'undefined' && module.exports) {
        module.exports = Tablesort;
    } else {
        window.Tablesort = Tablesort;
    }
})();
<commit_msg>go all the way up to yottabytes<commit_after>;(function() {
    function Tablesort(el, options) {
        if (!el) throw new Error('Element not found');
        if (el.tagName !== 'TABLE') throw new Error('Element must be a table');
        this.init(el, options || {});
    }

    Tablesort.prototype = {

        init: function(el, options) {
            var that = this,
                firstRow;
            this.thead = false;
            this.options = options;

            if (el.rows && el.rows.length > 0) {
                if (el.tHead && el.tHead.rows.length > 0) {
                    firstRow = el.tHead.rows[el.tHead.rows.length - 1];
                    that.thead = true;
                } else {
                    firstRow = el.rows[0];
                }
            }

            if (!firstRow) return;

            var onClick = function() {
                if (that.current && that.current !== this) {
                    if (that.current.classList.contains(classSortUp)) {
                        that.current.classList.remove(classSortUp);
                    }
                    else if (that.current.classList.contains(classSortDown)) {
                        that.current.classList.remove(classSortDown);
                    }
                }

                that.current = this;
                that.sortTable(this);
            };

            var defaultSort;

            // Assume first row is the header and attach a click handler to each.
            for (var i = 0; i < firstRow.cells.length; i++) {
                var cell = firstRow.cells[i];
                if (!cell.classList.contains('no-sort')) {
                    cell.classList.add('sort-header');
                    cell.addEventListener('click', onClick, false);

                    if (cell.classList.contains('sort-default')) {
                        defaultSort = cell;
                    }
                }
            }

            if (defaultSort) {
                that.current = defaultSort;
                that.sortTable(defaultSort, true);
            }
        },

        getFirstDataRowIndex: function() {
            // If table does not have a <thead>, assume that first row is
            // a header and skip it.
            if (!this.thead) {
                return 1;
            } else {
                return 0;
            }
        },

        sortTable: function(header, update) {
            var that = this,
                column = header.cellIndex,
                sortFunction,
                t = getParent(header, 'table'),
                item = '',
                i = that.getFirstDataRowIndex();

            if (t.rows.length <= 1) return;

            while (item === '' && i < t.tBodies[0].rows.length) {
                item = getInnerText(t.tBodies[0].rows[i].cells[column]);
                item = item.trim();
                // Exclude cell values where commented out HTML exists
                if (item.substr(0, 4) === '<!--' || item.length === 0) {
                    item = '';
                }
                i++;
            }

            if (item === '') return;

            // Possible sortFunction scenarios
            var sortCaseInsensitive = function(a, b) {
                var aa = getInnerText(a.cells[that.col]).toLowerCase(),
                    bb = getInnerText(b.cells[that.col]).toLowerCase();

                if (aa === bb) return 0;
                if (aa < bb) return 1;

                return -1;
            };

            var sortNumber = function(a, b) {
                var aa = getInnerText(a.cells[that.col]),
                    bb = getInnerText(b.cells[that.col]);

                aa = cleanNumber(aa);
                bb = cleanNumber(bb);
                return compareNumber(bb, aa);
            };

            var sortDate = function(a, b) {
                var aa = getInnerText(a.cells[that.col]).toLowerCase(),
                    bb = getInnerText(b.cells[that.col]).toLowerCase();
                return parseDate(bb) - parseDate(aa);
            };

            var sortDotSep = function(a, b) {
                var aa = getInnerText(a.cells[that.col]).split('.'),
                    bb = getInnerText(b.cells[that.col]).split('.');

                for (var i = 0, len = aa.length; i < len; i++) {
                    var aai = parseInt(aa[i]),
                        bbi = parseInt(bb[i]);

                    if (aai == bbi) continue;
                    if (aai < bbi) return -1;
                    if (aai > bbi) return 1;
                }
                return 0;
            };

            var sortFilesize = function(a, b) {
                var aa = filesize2num(getInnerText(a.cells[that.col]))
                    bb = filesize2num(getInnerText(b.cells[that.col]));

                return compareNumber(bb, aa);
            };

            // Sort dot separted numbers, e.g. ip addresses or version numbers
            if (/^(\d+\.)+\d+$/.test(item)) {
                sortFunction = sortDotSep;
            }
            // sort filesize, e.g. "123.45 MB"
            else if (/^\d+(\.\d+)? ?(k|M|G|T|P|E|Z|Y)?i?B?$/i.test(item)) {
                sortFunction = sortFilesize;
            }
            // Sort as number if a currency key exists or number
            else if (item.match(/^-?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/) || // prefixed currency
                item.match(/^-?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/) || // suffixed currency
                item.match(/^-?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/) // number
            ) {
                sortFunction = sortNumber;
            } else if (testDate(item)) {
                sortFunction = sortDate;
            } else {
                sortFunction = sortCaseInsensitive;
            }

            this.col = column;
            var newRows = [],
                noSorts = {},
                j,
                totalRows = 0;

            for (i = 0; i < t.tBodies.length; i++) {
                for (j = 0; j < t.tBodies[i].rows.length; j++) {
                    var tr = t.tBodies[i].rows[j];
                    if (tr.classList.contains('no-sort')) {
                        // keep no-sorts in separate list to be able to insert
                        // them back at their original position later
                        noSorts[totalRows] = tr;
                    } else {
                        // Save the index for stable sorting
                        newRows.push({
                            tr: tr,
                            index: totalRows
                        });
                    }
                    totalRows++;
                }
            }

            var sortUp   = that.options.descending ? classSortDown : classSortUp,
                sortDown = that.options.descending ? classSortUp : classSortDown;

            if (!update) {
                if (header.classList.contains(sortUp)) {
                    header.classList.remove(sortUp);
                    header.classList.add(sortDown);
                } else {
                    header.classList.remove(sortDown);
                    header.classList.add(sortUp);
                }
            } else if (!header.classList.contains(sortUp) && !header.classList.contains(sortDown)) {
                header.classList.add(sortUp);
            }

            // Make a stable sort function
            var stabilize = function(sort) {
                return function(a, b) {
                    var unstableResult = sort(a.tr, b.tr);
                    if (unstableResult === 0) {
                        return a.index - b.index;
                    }
                    return unstableResult;
                };
            };

            // Make an `anti-stable` sort function. If two elements are equal
            // under the original sort function, then there relative order is
            // reversed.
            var antiStabilize = function(sort) {
                return function(a, b) {
                    var unstableResult = sort(a.tr, b.tr);
                    if (unstableResult === 0) {
                        return b.index - a.index;
                    }
                    return unstableResult;
                };
            };

            // Before we append should we reverse the new array or not?
            // If we reverse, the sort needs to be `anti-stable` so that
            // the double negatives cancel out
            if (header.classList.contains(classSortDown)) {
                newRows.sort(antiStabilize(sortFunction));
                newRows.reverse();
            } else {
                newRows.sort(stabilize(sortFunction));
            }

            // append rows that already exist rather than creating new ones
            var noSortsSoFar = 0;
            for (i = 0; i < totalRows; i++) {
                var whatToInsert;
                if (noSorts[i]) {
                    // We have a no-sort row for this position, insert it here.
                    whatToInsert = noSorts[i];
                    noSortsSoFar++;
                } else {
                    whatToInsert = newRows[i - noSortsSoFar].tr;
                }
                // appendChild(x) moves x if already present somewhere else in the DOM
                t.tBodies[0].appendChild(whatToInsert);
            }
        },

        refresh: function() {
            if (this.current !== undefined) {
                this.sortTable(this.current, true);
            }
        }
    };

    var classSortUp   = 'sort-up',
        classSortDown = 'sort-down';

    var week       = /(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\.?\,?\s*/i,
        commonDate = /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/,
        month      = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;

    var testDate = function(date) {
            return (
                date.search(week) !== -1 ||
                date.search(commonDate) !== -1  ||
                date.search(month !== -1)
            ) !== -1 && !isNaN(parseDate(date));
        },

        parseDate = function(date) {
            date = date.replace(/\-/g, '/');
            date = date.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, '$1/$2/$3'); // format before getTime
            return new Date(date).getTime();
        },

        getParent = function(el, pTagName) {
            if (el === null) {
                return null;
            } else if (el.nodeType === 1 && el.tagName.toLowerCase() === pTagName.toLowerCase()) {
                return el;
            } else {
                return getParent(el.parentNode, pTagName);
            }
        },

        getInnerText = function(el) {
            var that = this;

            if (typeof el === 'string' || typeof el === 'undefined') return el;

            var str = el.getAttribute('data-sort') || '';

            if (str) {
                return str;
            } else if (el.textContent) {
                return el.textContent;
            } else if (el.innerText) {
                return el.innerText;
            }

            var cs = el.childNodes,
                l = cs.length;

            for (var i = 0; i < l; i++) {
                switch (cs[i].nodeType) {
                    case 1:
                        // ELEMENT_NODE
                        str += that.getInnerText(cs[i]);
                    break;
                    case 3:
                        // TEXT_NODE
                        str += cs[i].nodeValue;
                    break;
                }
            }

            return str;
        },

        compareNumber = function(a, b) {
            var aa = parseFloat(a),
                bb = parseFloat(b);

            a = isNaN(aa) ? 0 : aa;
            b = isNaN(bb) ? 0 : bb;
            return a - b;
        },

        cleanNumber = function(i) {
            return i.replace(/[^\-?0-9.]/g, '');
        };

        // Converts filesize to bytes
        // Ex. filesize2num("123 KB") -> 123000
        // Ex. filesize2num("123 KiB") -> 125952
        filesize2num = function(filesize) {
            var matches = filesize.match(/^(\d+(\.\d+)?) ?((k|M|G|T|P|E|Z|Y)?i?B?)$/i);

            var num    = parseFloat(cleanNumber(matches[1])),
                suffix = matches[3];

            return num * suffix2num(suffix);
        };

        // Returns suffix multiplier
        // Ex. suffix2num("KB") -> 1000
        // Ex. suffix2num("KiB") -> 1024
        suffix2num = function(suffix) {
            suffix = suffix.toLowerCase();
            var base = suffix[1] === "i" ? 1024 : 1000;

            switch(suffix[0]) {
                case "k":
                    return Math.pow(base, 2);
                case "m":
                    return Math.pow(base, 3);
                case "g":
                    return Math.pow(base, 4);
                case "t":
                    return Math.pow(base, 5);
                case "p":
                    return Math.pow(base, 6);
                case "e":
                    return Math.pow(base, 7);
                case "z":
                    return Math.pow(base, 8);
                case "y":
                    return Math.pow(base, 9);
                default:
                    return base;
            }
        };

    if (typeof module !== 'undefined' && module.exports) {
        module.exports = Tablesort;
    } else {
        window.Tablesort = Tablesort;
    }
})();
<|endoftext|>


<commit_before>// Modified to redirect the contact form to formspree.io.
$(function() {

    $("input,textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            event.preventDefault(); // prevent default submit behaviour
            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, 1).join(' ');
            }
            $.ajax({
                url: "https://formspree.io/lars@media2b.net",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                dataType: "json",
                cache: false,
                success: function() {
                    // Success message
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Thanks " + firstName + ", your message has been sent. We will contact you as soon as we can!");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later or contact us directly via hello@xithing.io!");
                    $('#success > .alert-danger').append('
');
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

/*When clicking on Full hide fail/success boxes */
$('#name').focus(function() {
    $('#success').html('');
});
<commit_msg>Deploy<commit_after>// Modified to redirect the contact form to formspree.io.
$(function() {

    $("input,textarea").jqBootstrapValidation({
        preventSubmit: true,
        submitError: function($form, event, errors) {
            // additional error messages or events
        },
        submitSuccess: function($form, event) {
            event.preventDefault(); // prevent default submit behaviour
            // get values from FORM
            var name = $("input#name").val();
            var email = $("input#email").val();
            var phone = $("input#phone").val();
            var message = $("textarea#message").val();
            var firstName = name; // For Success/Failure Message
            // Check for white space in name for Success/Fail message
            if (firstName.indexOf(' ') >= 0) {
                firstName = name.split(' ').slice(0, 1).join(' ');
            }
            $.ajax({
                url: "https://formspree.io/lars@media2b.net",
                type: "POST",
                data: {
                    name: name,
                    phone: phone,
                    email: email,
                    message: message
                },
                dataType: "json",
                cache: false,
                success: function() {
                    // Success message
                    $('#success').html("
");
                    $('#success > .alert-success').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-success')
                        .append("Thanks " + firstName + ", your message has been sent. We will contact you as soon as we can!");
                    $('#success > .alert-success')
                        .append('
');

                    //clear all fields
                    $('#contactForm').trigger("reset");
                },
                error: function() {
                    // Fail message
                    $('#success').html("
");
                    $('#success > .alert-danger').html("<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×")
                        .append("</button>");
                    $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later or contact us directly via info@xithing.io!");
                    $('#success > .alert-danger').append('
');
                },
            })
        },
        filter: function() {
            return $(this).is(":visible");
        },
    });

    $("a[data-toggle=\"tab\"]").click(function(e) {
        e.preventDefault();
        $(this).tab("show");
    });
});

/*When clicking on Full hide fail/success boxes */
$('#name').focus(function() {
    $('#success').html('');
});
<|endoftext|>


<commit_before>window.app.views.SimPage = Backbone.View.extend({

  el: "#view",
  template: JST["app/templates/sim.us"],
  camera: null,
  scene: null,
  renderer: null,
  controls: null,
  transform: null,
  materials: [
    //new THREE.MeshLambertMaterial({ color: 0x000000 }), //Outer Floor Color
    ///new THREE.MeshLambertMaterial({ color: 0x666666 }), //Outer Roof Ceiling Color
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, opacity: 0.1, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors }),
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, wireframe: true }), //WireFrame or Wall color
    //new THREE.MeshLambertMaterial({ color: 0xe0ffff, opacity: 0.8, depthWrite: false, depthTest: false, vertexColors: THREE.NoColors }) //Window Color etc
  ],

  events: {
    'click #run-button' : 'runSimulation',
    'change .group-disable' : 'handleSliderCheckboxChange'
  },

  // Create a transform matrix that will put the center of a zone at the origin (0, 0, 0)
  buildOriginTransform: function(zone) {
    var dimensions = { x: {}, y: {}, z: {} };
    _.each(zone, function(surface) {
      if (surface.BuildingSurface) {
        _.each(surface.BuildingSurface.vertices, function(vertex) {
          if (dimensions.x.min === undefined || vertex.x < dimensions.x.min) { dimensions.x.min = vertex.x; }
          if (dimensions.x.max === undefined || vertex.x > dimensions.x.max) { dimensions.x.max = vertex.x; }
          if (dimensions.y.min === undefined || vertex.y < dimensions.y.min) { dimensions.y.min = vertex.y; }
          if (dimensions.y.max === undefined || vertex.y > dimensions.y.max) { dimensions.y.max = vertex.y; }
          if (dimensions.z.min === undefined || vertex.z < dimensions.z.min) { dimensions.z.min = vertex.z; }
          if (dimensions.z.max === undefined || vertex.z > dimensions.z.max) { dimensions.z.max = vertex.z; }
        });
      }
    });

    return new THREE.Matrix4().makeTranslation(-dimensions.x.min-(dimensions.x.max-dimensions.x.min)/2,
      -dimensions.y.min-(dimensions.y.max-dimensions.y.min)/2,
      -dimensions.z.min-(dimensions.z.max-dimensions.z.min)/2);
  },

  getMaterialIndex: function(surfaceType) {
    switch (surfaceType) {
      case 'Floor':
        return 0;
      case 'Ceiling':
        return 1;
      case 'Wall':
        return 2;
      case 'Window':
        return 3;
    }
    return -1;
  },

  createGeometry: function(surface, transform) {
    var geometry = new THREE.PlaneGeometry();
    geometry.vertices = [];
    _.each(surface.vertices, function(vertex) {
      var vector = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
      vector.applyMatrix4(transform);
      geometry.vertices.push(vector);
    });
    return geometry;
  },

  createFaces: function(surface) {
    var faces = [];
    var face = new THREE.Face3(0, 1, 2);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    face = new THREE.Face3(2, 3, 0);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    return faces;
  },

  addPlane: function(scene, surface, transform) {
      var geometry = this.createGeometry(surface, transform);
      geometry.faces = this.createFaces(surface);
      geometry.computeBoundingSphere();
      geometry.computeFaceNormals();
      geometry.computeCentroids();
      geometry.name = surface.name;
      geometry.type = surface.type;

      //var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: 0.5 });
      //var plane = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(this.materials));
      //plane.castShadow=true;
      //scene.add(plane);
      var axes = new THREE.AxisHelper (20);
      scene.add(axes);
      var cubeGeometry=new THREE.CubeGeometry(4,3,4);
      var cubeMaterial= new THREE.MeshLambertMaterial({color: 0xf2d478});
      var solarCube = new THREE.Mesh(cubeGeometry, cubeMaterial);
      solarCube.position.x=0;
      solarCube.position.y=0;
      solarCube.position.z=2;
      solarCube.castShadow=true;
      scene.add(solarCube);

      var groundPlaneGeometry = new THREE.PlaneGeometry (100,100);
      var groundPlaneMaterial= new THREE.MeshLambertMaterial ({color: 0xcccccc});
      var groundPlane = new THREE.Mesh (groundPlaneGeometry, groundPlaneMaterial);
      groundPlane.position.x=0;
      groundPlane.position.y=0;
      groundPlane.position.z=-0;
      groundPlane.receiveShadow=true;
      scene.add(groundPlane);

      var sunLight= new THREE.DirectionalLight (0xffffff, 0.1);
      sunLight.position.set(45,45,100);
      sunLight.castShadow=true;
      sunLight.onlyShadow=false;
      scene.add(sunLight);

      //var ambilight = new THREE.AmbientLight( 0x404040 ); // soft white light
      //scene.add( ambilight );
  },

  addPlanes: function(scene, surfaces, transform) {
    var self = this;
    _.each(surfaces, function(surface) {
      if (surface.BuildingSurface) {
        self.addPlane(scene, surface.BuildingSurface, transform);
      } else if (surface.FenestrationSurface) {
        self.addPlane(scene, surface.FenestrationSurface, transform);
      }
    });
  },

  createCamera: function(canvas) {
    var camera = new THREE.PerspectiveCamera(60, canvas.innerWidth() / canvas.innerHeight(), 1, 1000);
    camera.position.x = -10;
    camera.position.y = 10;
    camera.position.z = 5;
    //camera.lookAt(scene.position);
    return camera;
  },

  createCanvasControls: function(canvas) {
    var controls = new THREE.TrackballControls(this.camera, canvas[0]);
    controls.rotateSpeed = 0.1;
    controls.zoomSpeed = 0.5;
    controls.panSpeed = 0.7;
    controls.noZoom = false;
    controls.noPan = false;
    controls.staticMoving = false;
    controls.dynamicDampingFactor = 0.3;
    controls.keys = [ 65, 83, 68 ];
    return controls;
  },

  createManualControls: function() {
    var self = this;
    $("#Length").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#Depth").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#Height").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#NWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#EWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#SWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#WWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#insulation-level").slider({ min: 1, max: 10, value: 5 });
    $("#ventilation-rate").slider({ min: 1, max: 10, value: 5 });
    $("#run-button").button();

    this.setSliderDisplayValue('#Length');
    this.setSliderDisplayValue('#Depth');
    this.setSliderDisplayValue('#Height');
    this.setSliderDisplayValue('#NWinGR');
    this.setSliderDisplayValue('#EWinGR');
    this.setSliderDisplayValue('#SWinGR');
    this.setSliderDisplayValue('#WWinGR');
  },

  handleSliderCheckboxChange: function(event) {
    var grId = '#'+event.target.id+'GR';
    $(grId).slider(event.target.checked ? "enable" : "disable");
    if (event.target.checked) {
      $(grId+'Disp').show();
    } else {
      $(grId+'Disp').hide();
    }
  },

  updateSliderDisplay: function(event, ui) {
    this.setSliderDisplayValue('#'+event.target.id, ui.value);
  },

  setSliderDisplayValue: function(sliderId, val) {
    if (val === undefined) {
      val = $(sliderId).slider('value');
    }
    $(sliderId+'Disp').text(val);
  },

  createRenderer: function(canvas) {
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColorHex(0xffffff, 1.0);
    renderer.setSize(canvas.innerWidth(), canvas.innerHeight());
    renderer.shadowMapEnabled=true;
    return renderer;
  },

  init: function() {
    var self = this;
    var container = $("#canvas");
    this.scene = new THREE.Scene();
    this.camera = this.createCamera(container);
    this.controls = this.createCanvasControls(container);
    this.createManualControls();
    this.renderer = this.createRenderer(container);
    container[0].appendChild(this.renderer.domElement);

    $.get('simulation/load/25', function(d) {
      if (d.status === 0) {
        transform = self.buildOriginTransform(d.data.zone);
        self.addPlanes(self.scene, d.data.zone, transform);
      }
    });

    window.addEventListener('resize', function() {
      var canvas = $('#canvas');
      var width = canvas.innerWidth();
      var height = canvas.innerHeight();
      renderer.setSize(width, height);
      this.camera.aspect = width / height;
      this.camera.updateProjectionMatrix();
    });
  },

  animate: function(controls, renderer) {
    window.requestAnimationFrame(this.animate);
    this.controls.update();
    this.renderer.render(this.scene, this.camera);
  },

  runSimulation: function() {
    var zone = [];
    var inverseTransform = new THREE.Matrix4();

    var progbar = $('#progressbar');

    $("#run-button").button("option", "disabled", true);
    progbar.progressbar({ value: false });
    progbar.show();

    inverseTransform.getInverse(transform);
    _.each(this.scene.children, function(child) {
      var result = {};
      result.name = child.geometry.name;
      result.type = child.geometry.type;
      result.vertices = [];

      _.each(child.geometry.vertices, function(vertex) {
        var vert = vertex.clone().applyMatrix4(inverseTransform);
        result.vertices.push({x: vert.x, y: vert.y, z: vert.z});
      });

      zone.push(result);
    });

    $.post('simulation/run', { zone: zone }, this.getSimulationResults);
  },

  getSimulationResults: function(data, status) {
    //window.console.log(data);
    //window.console.log(status);
    $.get('simulation/' + data.jobId, this.handleSimulationResults.bind(this));
    // TODO: add some error checking
  },

  handleSimulationResults: function(results, status, xhr) {
    if (xhr.status === 200) {
      //window.console.log('simulation run finished');
      var progbar = $('#progressbar');
      progbar.hide();
      progbar.progressbar('destroy');
      $("#run-button").button("option", "disabled", false);
      $("#results").text(results);
    } else {
      //window.console.log('simulation not finished');
      window.setTimeout(this.getSimulationResults.bind(this, results), 5000);
    }
  },

  initialize: function(options) {
    _.bindAll(this);
    this.model = new window.app.models.Simulation();
  },

  render: function() {
    var self = this;
    this.$el.html(this.template(this.model.attributes));
    window.setTimeout(function() {
      self.init();
      self.animate();
    }, 0);
    return this;
  }

});
<commit_msg>Kludge to get simulations to run again.<commit_after>window.app.views.SimPage = Backbone.View.extend({

  el: "#view",
  template: JST["app/templates/sim.us"],
  camera: null,
  scene: null,
  renderer: null,
  controls: null,
  transform: null,
  materials: [
    //new THREE.MeshLambertMaterial({ color: 0x000000 }), //Outer Floor Color
    ///new THREE.MeshLambertMaterial({ color: 0x666666 }), //Outer Roof Ceiling Color
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, opacity: 0.1, depthWrite: false, depthTest: false, vertexColors: THREE.VertexColors }),
    //new THREE.MeshLambertMaterial({ color: 0xcccccc, wireframe: true }), //WireFrame or Wall color
    //new THREE.MeshLambertMaterial({ color: 0xe0ffff, opacity: 0.8, depthWrite: false, depthTest: false, vertexColors: THREE.NoColors }) //Window Color etc
  ],

  events: {
    'click #run-button' : 'runSimulation',
    'change .group-disable' : 'handleSliderCheckboxChange'
  },

  // Create a transform matrix that will put the center of a zone at the origin (0, 0, 0)
  buildOriginTransform: function(zone) {
    var dimensions = { x: {}, y: {}, z: {} };
    _.each(zone, function(surface) {
      if (surface.BuildingSurface) {
        _.each(surface.BuildingSurface.vertices, function(vertex) {
          if (dimensions.x.min === undefined || vertex.x < dimensions.x.min) { dimensions.x.min = vertex.x; }
          if (dimensions.x.max === undefined || vertex.x > dimensions.x.max) { dimensions.x.max = vertex.x; }
          if (dimensions.y.min === undefined || vertex.y < dimensions.y.min) { dimensions.y.min = vertex.y; }
          if (dimensions.y.max === undefined || vertex.y > dimensions.y.max) { dimensions.y.max = vertex.y; }
          if (dimensions.z.min === undefined || vertex.z < dimensions.z.min) { dimensions.z.min = vertex.z; }
          if (dimensions.z.max === undefined || vertex.z > dimensions.z.max) { dimensions.z.max = vertex.z; }
        });
      }
    });

    return new THREE.Matrix4().makeTranslation(-dimensions.x.min-(dimensions.x.max-dimensions.x.min)/2,
      -dimensions.y.min-(dimensions.y.max-dimensions.y.min)/2,
      -dimensions.z.min-(dimensions.z.max-dimensions.z.min)/2);
  },

  getMaterialIndex: function(surfaceType) {
    switch (surfaceType) {
      case 'Floor':
        return 0;
      case 'Ceiling':
        return 1;
      case 'Wall':
        return 2;
      case 'Window':
        return 3;
    }
    return -1;
  },

  createGeometry: function(surface, transform) {
    var geometry = new THREE.PlaneGeometry();
    geometry.vertices = [];
    _.each(surface.vertices, function(vertex) {
      var vector = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
      vector.applyMatrix4(transform);
      geometry.vertices.push(vector);
    });
    return geometry;
  },

  createFaces: function(surface) {
    var faces = [];
    var face = new THREE.Face3(0, 1, 2);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    face = new THREE.Face3(2, 3, 0);
    face.materialIndex = this.getMaterialIndex(surface.type);
    faces.push(face);
    return faces;
  },

  addPlane: function(scene, surface, transform) {
      var geometry = this.createGeometry(surface, transform);
      geometry.faces = this.createFaces(surface);
      geometry.computeBoundingSphere();
      geometry.computeFaceNormals();
      geometry.computeCentroids();
      geometry.name = surface.name;
      geometry.type = surface.type;

      //var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.FaceColors, overdraw: 0.5 });
      //var plane = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(this.materials));
      //plane.castShadow=true;
      //scene.add(plane);
      var axes = new THREE.AxisHelper (20);
      scene.add(axes);
      var cubeGeometry=new THREE.CubeGeometry(4,3,4);
      var cubeMaterial= new THREE.MeshLambertMaterial({color: 0xf2d478});
      var solarCube = new THREE.Mesh(cubeGeometry, cubeMaterial);
      solarCube.position.x=0;
      solarCube.position.y=0;
      solarCube.position.z=2;
      solarCube.castShadow=true;
      scene.add(solarCube);

      var groundPlaneGeometry = new THREE.PlaneGeometry (100,100);
      var groundPlaneMaterial= new THREE.MeshLambertMaterial ({color: 0xcccccc});
      var groundPlane = new THREE.Mesh (groundPlaneGeometry, groundPlaneMaterial);
      groundPlane.position.x=0;
      groundPlane.position.y=0;
      groundPlane.position.z=-0;
      groundPlane.receiveShadow=true;
      scene.add(groundPlane);

      var sunLight= new THREE.DirectionalLight (0xffffff, 0.1);
      sunLight.position.set(45,45,100);
      sunLight.castShadow=true;
      sunLight.onlyShadow=false;
      scene.add(sunLight);

      //var ambilight = new THREE.AmbientLight( 0x404040 ); // soft white light
      //scene.add( ambilight );
  },

  addPlanes: function(scene, surfaces, transform) {
    var self = this;
    _.each(surfaces, function(surface) {
      if (surface.BuildingSurface) {
        self.addPlane(scene, surface.BuildingSurface, transform);
      } else if (surface.FenestrationSurface) {
        self.addPlane(scene, surface.FenestrationSurface, transform);
      }
    });
  },

  createCamera: function(canvas) {
    var camera = new THREE.PerspectiveCamera(60, canvas.innerWidth() / canvas.innerHeight(), 1, 1000);
    camera.position.x = -10;
    camera.position.y = 10;
    camera.position.z = 5;
    //camera.lookAt(scene.position);
    return camera;
  },

  createCanvasControls: function(canvas) {
    var controls = new THREE.TrackballControls(this.camera, canvas[0]);
    controls.rotateSpeed = 0.1;
    controls.zoomSpeed = 0.5;
    controls.panSpeed = 0.7;
    controls.noZoom = false;
    controls.noPan = false;
    controls.staticMoving = false;
    controls.dynamicDampingFactor = 0.3;
    controls.keys = [ 65, 83, 68 ];
    return controls;
  },

  createManualControls: function() {
    var self = this;
    $("#Length").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#Depth").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#Height").slider({ min: 1, max: 10, value: 5, slide: self.updateSliderDisplay });
    $("#NWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#EWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#SWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#WWinGR").slider({ min: 1, max: 99, value: 50, disabled: true, slide: self.updateSliderDisplay });
    $("#insulation-level").slider({ min: 1, max: 10, value: 5 });
    $("#ventilation-rate").slider({ min: 1, max: 10, value: 5 });
    $("#run-button").button();

    this.setSliderDisplayValue('#Length');
    this.setSliderDisplayValue('#Depth');
    this.setSliderDisplayValue('#Height');
    this.setSliderDisplayValue('#NWinGR');
    this.setSliderDisplayValue('#EWinGR');
    this.setSliderDisplayValue('#SWinGR');
    this.setSliderDisplayValue('#WWinGR');
  },

  handleSliderCheckboxChange: function(event) {
    var grId = '#'+event.target.id+'GR';
    $(grId).slider(event.target.checked ? "enable" : "disable");
    if (event.target.checked) {
      $(grId+'Disp').show();
    } else {
      $(grId+'Disp').hide();
    }
  },

  updateSliderDisplay: function(event, ui) {
    this.setSliderDisplayValue('#'+event.target.id, ui.value);
  },

  setSliderDisplayValue: function(sliderId, val) {
    if (val === undefined) {
      val = $(sliderId).slider('value');
    }
    $(sliderId+'Disp').text(val);
  },

  createRenderer: function(canvas) {
    var renderer = new THREE.WebGLRenderer();
    renderer.setClearColorHex(0xffffff, 1.0);
    renderer.setSize(canvas.innerWidth(), canvas.innerHeight());
    renderer.shadowMapEnabled=true;
    return renderer;
  },

  init: function() {
    var self = this;
    var container = $("#canvas");
    this.scene = new THREE.Scene();
    this.camera = this.createCamera(container);
    this.controls = this.createCanvasControls(container);
    this.createManualControls();
    this.renderer = this.createRenderer(container);
    container[0].appendChild(this.renderer.domElement);

    $.get('simulation/load/25', function(d) {
      if (d.status === 0) {
        transform = self.buildOriginTransform(d.data.zone);
        self.addPlanes(self.scene, d.data.zone, transform);
      }
    });

    window.addEventListener('resize', function() {
      var canvas = $('#canvas');
      var width = canvas.innerWidth();
      var height = canvas.innerHeight();
      renderer.setSize(width, height);
      this.camera.aspect = width / height;
      this.camera.updateProjectionMatrix();
    });
  },

  animate: function(controls, renderer) {
    window.requestAnimationFrame(this.animate);
    this.controls.update();
    this.renderer.render(this.scene, this.camera);
  },

  runSimulation: function() {
    var zone = [];
    var inverseTransform = new THREE.Matrix4();

    var progbar = $('#progressbar');

    $("#run-button").button("option", "disabled", true);
    progbar.progressbar({ value: false });
    progbar.show();

    /* TODO: this needs to be changed after Troy's changes
    inverseTransform.getInverse(transform);
    _.each(this.scene.children, function(child) {
      var result = {};
      result.name = child.geometry.name;
      result.type = child.geometry.type;
      result.vertices = [];

      _.each(child.geometry.vertices, function(vertex) {
        var vert = vertex.clone().applyMatrix4(inverseTransform);
        result.vertices.push({x: vert.x, y: vert.y, z: vert.z});
      });

      zone.push(result);
    });
    */
    // this is a kludge for now
    zone.push({name: "test", type: "roof", vertices: [{x: 10, y: 10, z: 10 }]});

    $.post('simulation/run', { zone: zone }, this.getSimulationResults);
  },

  getSimulationResults: function(data, status) {
    //window.console.log(data);
    //window.console.log(status);
    $.get('simulation/' + data.jobId, this.handleSimulationResults.bind(this));
    // TODO: add some error checking
  },

  handleSimulationResults: function(results, status, xhr) {
    if (xhr.status === 200) {
      //window.console.log('simulation run finished');
      var progbar = $('#progressbar');
      progbar.hide();
      progbar.progressbar('destroy');
      $("#run-button").button("option", "disabled", false);
      $("#results").text(results);
    } else {
      //window.console.log('simulation not finished');
      window.setTimeout(this.getSimulationResults.bind(this, results), 5000);
    }
  },

  initialize: function(options) {
    _.bindAll(this);
    this.model = new window.app.models.Simulation();
  },

  render: function() {
    var self = this;
    this.$el.html(this.template(this.model.attributes));
    window.setTimeout(function() {
      self.init();
      self.animate();
    }, 0);
    return this;
  }

});
<|endoftext|>


<commit_before>angular.module('ui.bootstrap.tabs', [])

.controller('UibTabsetController', ['$scope', function ($scope) {
  var ctrl = this,
    oldIndex;
  ctrl.tabs = [];

  ctrl.select = function(index, evt) {
    if (!destroyed) {
      var previousIndex = findTabIndex(oldIndex);
      var previousSelected = ctrl.tabs[previousIndex];
      if (previousSelected) {
        previousSelected.tab.onDeselect({
          $event: evt
        });
        if (evt && evt.defaultPrevented) {
          return;
        }
        previousSelected.tab.active = false;
      }

      var selected = ctrl.tabs[index];
      if (selected) {
        selected.tab.onSelect({
          $event: evt
        });
        selected.tab.active = true;
        ctrl.active = selected.index;
        oldIndex = selected.index;
      } else if (!selected && angular.isNumber(oldIndex)) {
        ctrl.active = null;
        oldIndex = null;
      }
    }
  };

  ctrl.addTab = function addTab(tab) {
    ctrl.tabs.push({
      tab: tab,
      index: tab.index
    });
    ctrl.tabs.sort(function(t1, t2) {
      if (t1.index > t2.index) {
        return 1;
      }

      if (t1.index < t2.index) {
        return -1;
      }

      return 0;
    });

    if (tab.index === ctrl.active || !angular.isNumber(ctrl.active) && ctrl.tabs.length === 1) {
      var newActiveIndex = findTabIndex(tab.index);
      ctrl.select(newActiveIndex);
    }
  };

  ctrl.removeTab = function removeTab(tab) {
    var index;
    for (var i = 0; i < ctrl.tabs.length; i++) {
      if (ctrl.tabs[i].tab === tab) {
        index = i;
        break;
      }
    }

    if (ctrl.tabs[index].index === ctrl.active) {
      var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
        index - 1 : index + 1 % ctrl.tabs.length;
      ctrl.select(newActiveTabIndex);
    }

    ctrl.tabs.splice(index, 1);
  };

  $scope.$watch('tabset.active', function(val) {
    if (angular.isNumber(val) && val !== oldIndex) {
      ctrl.select(findTabIndex(val));
    }
  });

  var destroyed;
  $scope.$on('$destroy', function() {
    destroyed = true;
  });

  function findTabIndex(index) {
    for (var i = 0; i < ctrl.tabs.length; i++) {
      if (ctrl.tabs[i].index === index) {
        return i;
      }
    }
  }
}])

.directive('uibTabset', function() {
  return {
    transclude: true,
    replace: true,
    scope: {},
    bindToController: {
      active: '=?',
      type: '@'
    },
    controller: 'UibTabsetController',
    controllerAs: 'tabset',
    templateUrl: function(element, attrs) {
      return attrs.templateUrl || 'uib/template/tabs/tabset.html';
    },
    link: function(scope, element, attrs) {
      scope.vertical = angular.isDefined(attrs.vertical) ?
        scope.$parent.$eval(attrs.vertical) : false;
      scope.justified = angular.isDefined(attrs.justified) ?
        scope.$parent.$eval(attrs.justified) : false;
      if (angular.isUndefined(attrs.active)) {
        scope.active = 0;
      }
    }
  };
})

.directive('uibTab', ['$parse', function($parse) {
  return {
    require: '^uibTabset',
    replace: true,
    templateUrl: function(element, attrs) {
      return attrs.templateUrl || 'uib/template/tabs/tab.html';
    },
    transclude: true,
    scope: {
      heading: '@',
      index: '=?',
      classes: '@?',
      onSelect: '&select', //This callback is called in contentHeadingTransclude
                          //once it inserts the tab's content into the dom
      onDeselect: '&deselect'
    },
    controller: function() {
      //Empty controller so other directives can require being 'under' a tab
    },
    controllerAs: 'tab',
    link: function(scope, elm, attrs, tabsetCtrl, transclude) {
      scope.disabled = false;
      if (attrs.disable) {
        scope.$parent.$watch($parse(attrs.disable), function(value) {
          scope.disabled = !! value;
        });
      }

      if (angular.isUndefined(attrs.index)) {
        if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
          scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
        } else {
          scope.index = 0;
        }
      }

      if (angular.isUndefined(attrs.classes)) {
        scope.classes = '';
      }

      scope.select = function(evt) {
        if (!scope.disabled) {
          var index;
          for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
            if (tabsetCtrl.tabs[i].tab === scope) {
              index = i;
              break;
            }
          }

          tabsetCtrl.select(index, evt);
        }
      };

      tabsetCtrl.addTab(scope);
      scope.$on('$destroy', function() {
        tabsetCtrl.removeTab(scope);
      });

      //We need to transclude later, once the content container is ready.
      //when this link happens, we're inside a tab heading.
      scope.$transcludeFn = transclude;
    }
  };
}])

.directive('uibTabHeadingTransclude', function() {
  return {
    restrict: 'A',
    require: '^uibTab',
    link: function(scope, elm) {
      scope.$watch('headingElement', function updateHeadingElement(heading) {
        if (heading) {
          elm.html('');
          elm.append(heading);
        }
      });
    }
  };
})

.directive('uibTabContentTransclude', function() {
  return {
    restrict: 'A',
    require: '^uibTabset',
    link: function(scope, elm, attrs) {
      var tab = scope.$eval(attrs.uibTabContentTransclude).tab;

      //Now our tab is ready to be transcluded: both the tab heading area
      //and the tab content area are loaded.  Transclude 'em both.
      tab.$transcludeFn(tab.$parent, function(contents) {
        angular.forEach(contents, function(node) {
          if (isTabHeading(node)) {
            //Let tabHeadingTransclude know.
            tab.headingElement = node;
          } else {
            elm.append(node);
          }
        });
      });
    }
  };

  function isTabHeading(node) {
    return node.tagName && (
      node.hasAttribute('uib-tab-heading') ||
      node.hasAttribute('data-uib-tab-heading') ||
      node.hasAttribute('x-uib-tab-heading') ||
      node.tagName.toLowerCase() === 'uib-tab-heading' ||
      node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
      node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
      node.tagName.toLowerCase() === 'uib:tab-heading'
    );
  }
});
<commit_msg>fix(tab): fix event handler call<commit_after>angular.module('ui.bootstrap.tabs', [])

.controller('UibTabsetController', ['$scope', function ($scope) {
  var ctrl = this,
    oldIndex;
  ctrl.tabs = [];

  ctrl.select = function(index, evt) {
    if (!destroyed) {
      var previousIndex = findTabIndex(oldIndex);
      var previousSelected = ctrl.tabs[previousIndex];
      if (previousSelected) {
        previousSelected.tab.onDeselect({
          $event: evt
        });
        if (evt && evt.isDefaultPrevented()) {
          return;
        }
        previousSelected.tab.active = false;
      }

      var selected = ctrl.tabs[index];
      if (selected) {
        selected.tab.onSelect({
          $event: evt
        });
        selected.tab.active = true;
        ctrl.active = selected.index;
        oldIndex = selected.index;
      } else if (!selected && angular.isNumber(oldIndex)) {
        ctrl.active = null;
        oldIndex = null;
      }
    }
  };

  ctrl.addTab = function addTab(tab) {
    ctrl.tabs.push({
      tab: tab,
      index: tab.index
    });
    ctrl.tabs.sort(function(t1, t2) {
      if (t1.index > t2.index) {
        return 1;
      }

      if (t1.index < t2.index) {
        return -1;
      }

      return 0;
    });

    if (tab.index === ctrl.active || !angular.isNumber(ctrl.active) && ctrl.tabs.length === 1) {
      var newActiveIndex = findTabIndex(tab.index);
      ctrl.select(newActiveIndex);
    }
  };

  ctrl.removeTab = function removeTab(tab) {
    var index;
    for (var i = 0; i < ctrl.tabs.length; i++) {
      if (ctrl.tabs[i].tab === tab) {
        index = i;
        break;
      }
    }

    if (ctrl.tabs[index].index === ctrl.active) {
      var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
        index - 1 : index + 1 % ctrl.tabs.length;
      ctrl.select(newActiveTabIndex);
    }

    ctrl.tabs.splice(index, 1);
  };

  $scope.$watch('tabset.active', function(val) {
    if (angular.isNumber(val) && val !== oldIndex) {
      ctrl.select(findTabIndex(val));
    }
  });

  var destroyed;
  $scope.$on('$destroy', function() {
    destroyed = true;
  });

  function findTabIndex(index) {
    for (var i = 0; i < ctrl.tabs.length; i++) {
      if (ctrl.tabs[i].index === index) {
        return i;
      }
    }
  }
}])

.directive('uibTabset', function() {
  return {
    transclude: true,
    replace: true,
    scope: {},
    bindToController: {
      active: '=?',
      type: '@'
    },
    controller: 'UibTabsetController',
    controllerAs: 'tabset',
    templateUrl: function(element, attrs) {
      return attrs.templateUrl || 'uib/template/tabs/tabset.html';
    },
    link: function(scope, element, attrs) {
      scope.vertical = angular.isDefined(attrs.vertical) ?
        scope.$parent.$eval(attrs.vertical) : false;
      scope.justified = angular.isDefined(attrs.justified) ?
        scope.$parent.$eval(attrs.justified) : false;
      if (angular.isUndefined(attrs.active)) {
        scope.active = 0;
      }
    }
  };
})

.directive('uibTab', ['$parse', function($parse) {
  return {
    require: '^uibTabset',
    replace: true,
    templateUrl: function(element, attrs) {
      return attrs.templateUrl || 'uib/template/tabs/tab.html';
    },
    transclude: true,
    scope: {
      heading: '@',
      index: '=?',
      classes: '@?',
      onSelect: '&select', //This callback is called in contentHeadingTransclude
                          //once it inserts the tab's content into the dom
      onDeselect: '&deselect'
    },
    controller: function() {
      //Empty controller so other directives can require being 'under' a tab
    },
    controllerAs: 'tab',
    link: function(scope, elm, attrs, tabsetCtrl, transclude) {
      scope.disabled = false;
      if (attrs.disable) {
        scope.$parent.$watch($parse(attrs.disable), function(value) {
          scope.disabled = !! value;
        });
      }

      if (angular.isUndefined(attrs.index)) {
        if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
          scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
        } else {
          scope.index = 0;
        }
      }

      if (angular.isUndefined(attrs.classes)) {
        scope.classes = '';
      }

      scope.select = function(evt) {
        if (!scope.disabled) {
          var index;
          for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
            if (tabsetCtrl.tabs[i].tab === scope) {
              index = i;
              break;
            }
          }

          tabsetCtrl.select(index, evt);
        }
      };

      tabsetCtrl.addTab(scope);
      scope.$on('$destroy', function() {
        tabsetCtrl.removeTab(scope);
      });

      //We need to transclude later, once the content container is ready.
      //when this link happens, we're inside a tab heading.
      scope.$transcludeFn = transclude;
    }
  };
}])

.directive('uibTabHeadingTransclude', function() {
  return {
    restrict: 'A',
    require: '^uibTab',
    link: function(scope, elm) {
      scope.$watch('headingElement', function updateHeadingElement(heading) {
        if (heading) {
          elm.html('');
          elm.append(heading);
        }
      });
    }
  };
})

.directive('uibTabContentTransclude', function() {
  return {
    restrict: 'A',
    require: '^uibTabset',
    link: function(scope, elm, attrs) {
      var tab = scope.$eval(attrs.uibTabContentTransclude).tab;

      //Now our tab is ready to be transcluded: both the tab heading area
      //and the tab content area are loaded.  Transclude 'em both.
      tab.$transcludeFn(tab.$parent, function(contents) {
        angular.forEach(contents, function(node) {
          if (isTabHeading(node)) {
            //Let tabHeadingTransclude know.
            tab.headingElement = node;
          } else {
            elm.append(node);
          }
        });
      });
    }
  };

  function isTabHeading(node) {
    return node.tagName && (
      node.hasAttribute('uib-tab-heading') ||
      node.hasAttribute('data-uib-tab-heading') ||
      node.hasAttribute('x-uib-tab-heading') ||
      node.tagName.toLowerCase() === 'uib-tab-heading' ||
      node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
      node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
      node.tagName.toLowerCase() === 'uib:tab-heading'
    );
  }
});
<|endoftext|>


<commit_before>import macros from "../macros.sjs";
var helpers = require("./helpers");

//---------------------------------------------------------
// Indexer
//---------------------------------------------------------

function Indexer(program, handlers) {
  this.worker = program.worker
  this.system = program.system;
  this.tableToIndexes = {};
  this.indexes = {};
  this.tablesToForward = [];
  this.handlers = handlers || {};
};
module.exports.Indexer = Indexer;

Indexer.prototype = {
  latestDiffs: {},
  handleDiffs: function(diffs, fromProgram) {
    this.latestDiffs = diffs;
    var tableToIndexes = this.tableToIndexes;
    var indexes = this.indexes;
    var system = this.system;
    var cur;
    var specialDiffs = ["view", "field"];
    var isSpecial = false;
    foreach(table of specialDiffs) {
      if(!diffs[table] || !(diffs[table].adds || diffs[table].removes)) { continue; }
      applyDiff(system, table, diffs[table]);
      isSpecial = true;
    }

    if(isSpecial) {
      var viewsToClear = getNonInputWorkspaceViews();

      // Nuke indexes before the system nukes facts.
      foreach(table of viewsToClear) {
        if(!tableToIndexes[table]) { continue; }
        var diff = {adds: [], removes: this.facts(table)};
        foreach(index of tableToIndexes[table]) {
          if(!index || !this.indexes[index]) { continue; }
          var cur = this.indexes[index];
          cur.indexer(cur.index, diff);
        }
      }

      system.recompile();
      //all non-input views were just cleared, make sure the worker clears storage
      //so that we end up with the views getting repopulated correctly.
      this.worker.postMessage({type: "clearStorage", views: viewsToClear})
    }

    forattr(table, diff of diffs) {
      if(tableToIndexes[table]) {
        foreach(index of tableToIndexes[table]) {
          cur = this.indexes[index];
          cur.index = cur.indexer(cur.index, diff);
        }
      }
      if(specialDiffs.indexOf(table) !== -1) { continue; }
      applyDiff(system, table, diff);
    }

    //we should only forward diffs to the program if they weren't
    //from the program to bgin with.
    if(!fromProgram) {
      var toSend = {};
      foreach(table of this.tablesToForward) {
        if(!diffs[table]) continue;
        toSend[table] = diffs[table];
      }
      if(Object.keys(toSend).length) {
        this.worker.postMessage({type: "diffs", diffs: toSend});
      }
    }

    //if we forced a recompile, we shouldn't redraw until the worker comes back
    //with the latest diffs.
    if(!isSpecial && this.handlers.diffsHandled) {
      this.handlers.diffsHandled(diffs);
    }
  },
  facts: function(table) {
    return this.system.getStore(table).getFacts();
  },
  index: function(index) {
    var cur = this.indexes[index];
    if(!cur) throw new Error("No index named: " + index);
    return cur.index;
  },
  hasIndex: function(index) {
    return !!this.indexes[index];
  },
  addIndex: function(table, name, indexer) {
    if(!this.tableToIndexes[table]) {
      this.tableToIndexes[table] = [];
    }
    this.tableToIndexes[table].push(name);
    //initialize the index by sending an add of all the facts we have now.
    this.indexes[name] = {index: indexer(null, {adds: this.facts(table), removes: []}),
                          indexer: indexer};
  },
  removeIndex: function(table, name) {
    var tableIndexes = this.tableToIndexes[table];
    var ix = tableIndexes.indexOf(name);
    if(ix !== -1) {
      tableIndexes.splice(ix, 1);
    }
    if(!tableIndexes.length) {
      delete this.tableToIndexes[table];
    }
    delete this.indexes[name];
  },
  forward: function(table) {
    if(!table) { return; }
    else if(typeof table === "object" && table.length) {
      this.tablesToForward.push.apply(this.tablesToForward, table);
    } else {
      this.tablesToForward.push(table);
    }
  },
  unforward: function(table) {
    var ix = this.tablesToForward.indexOf(table);
    if(ix !== -1) {
      this.tablesToForward.splice(ix, 1);
    }
  },
  first: function(table) {
    return this.facts(table)[0];
  },
  last: function(table) {
    var facts = this.facts(table);
    return facts[facts.length - 1];
  }
};

//---------------------------------------------------------
// Fact Indexers
//---------------------------------------------------------

var indexers = {
  // Builds a lookup table from index 1 to index 2. [Fact] -> {[Ix1]: Ix2}
  makeLookup: function(keyIx, valueIx) {
    if(valueIx !== false) {
      return function(cur, diffs) {
        var final = cur || {};
        foreach(remove of diffs.removes) {
          delete final[remove[keyIx]];
        }
        foreach(add of diffs.adds) {
          final[add[keyIx]] = add[valueIx];
        }
        return final;
      }
    } else {
      return function(cur, diffs) {
        var final = cur || {};
        foreach(remove of diffs.removes) {
          delete final[remove[keyIx]];
        }
        foreach(add of diffs.adds) {
          final[add[keyIx]] = add;
        }
        return final;
      }
    }
  },
  // Builds a lookup table from indexes 1 and 2 to index3. [Fact] -> {[Ix1]: {[Ix2]: Ix3}}
  makeLookup2D: function(key1Ix, key2Ix, valueIx) {
    return function(cur, diffs) {
      var final = cur || {};
      foreach(add of diffs.adds) {
        var key1 = add[key1Ix];
        if(!final[key1]) {
          final[key1] = {};
        }
        var key2 = add[key2Ix];
        final[key1][key2] = add[valueIx];
      }
      foreach(remove of diffs.removes) {
        var key1 = remove[key1Ix];
        if(!final[key1]) {
          continue;
        }
        var key2 = remove[key2Ix];
        delete final[key1][key2];
      }

      return final;
    };
  },
  // Groups facts by specified indexes, in order of hierarchy. [Fact] -> {[Any]: [Fact]|Group}
  makeCollector: function(keyIx) {
    if(arguments.length === 1) {
      return function(cur, diffs) {
        var final = cur || {};
        foreach(remove of diffs.removes) {
          if(!final[remove[keyIx]]) continue;
          final[remove[keyIx]] = final[remove[keyIx]].filter(function(cur) {
            return !arrayEqual(cur, remove)
          });
        }

        foreach(add of diffs.adds) {
          if(!final[add[keyIx]]) {
            final[add[keyIx]] = [];
          }
          final[add[keyIx]].push(add);
        }

        garbageCollectIndex(final);
        return final;
      }
    } else {
      var keyIxes = [].slice.apply(arguments);
      var lastKeyIx = keyIxes.pop();
      return function(cur, diffs) {
        var final = cur || {};
        foreach(add of diffs.adds) {
          var keys = [];
          foreach(ix, keyIx of keyIxes) {
            keys[ix] = add[keyIx];
          }
          var cur = helpers.aget(final, keys, true);
          if(!cur[add[lastKeyIx]]) {
            cur[add[lastKeyIx]] = [];
          }
          cur[add[lastKeyIx]].push(add);
        }
        foreach(remove of diffs.removes) {
          var keys = [];
          foreach(ix, keyIx of keyIxes) {
            keys[ix] = remove[keyIx];
          }
          var cur = helpers.aget(final, keys, false);
          if(!cur || !cur[remove[lastKeyIx]]) { continue; }
          cur[remove[lastKeyIx]] = cur[remove[lastKeyIx]].filter(function(c) {
            return !arrayEqual(c, remove);
          });

        }
        garbageCollectIndex(final);
        return final;
      }
    }
  },
  // Sorts facts by specified indexes, in order of priority. [Fact] -> [Fact]
  makeSorter: function() {
    var sortIxes = [].slice.apply(arguments);
    return function(cur, diffs) {
      var final = cur || [];
      foreach(remove of diffs.removes) {
        foreach(ix, item of final) {
          if(arrayEqual(item, remove)) {
            final.splice(ix, 1);
            break;
          }
        }
      }

      // @NOTE: This can be optimized further by presorting adds and maintaining loIx as a sliding window.
      foreach(add of diffs.adds) {
        var loIx = 0;
        var hiIx = final.length;
        foreach(sortIx of sortIxes) {
          for(var ix = loIx; ix < hiIx; ix++) {
            var item = final[ix];
            if(add[sortIx] > item[sortIx]) {
              loIx = ix + 1;
            } else if(add[sortIx] < item[sortIx]) {
              hiIx = ix;
              break;
            }
          }
        }
        final.splice(loIx, 0, add);
      }

      return final;
    }
  }
};
module.exports.indexers = indexers;

//---------------------------------------------------------
// Index helpers
//---------------------------------------------------------

// Delete any keys or descendant keys which are empty.
function garbageCollectIndex(index) {
  forattr(key, group of index) {
    if(group instanceof Array) {
      if(!group || !group.length) {
        delete index[key];
      }
    } else if(typeof group === "object") {
      garbageCollectIndex(group);
      if(!Object.keys(group).length) {
        delete index[key];
      }
    }
  }
}
module.exports.garbageCollectIndex = garbageCollectIndex;

function hasTag(id, needle) {
  var tags = indexer.index("idToTags")[id];
  foreach(tagEntry of tags) {
    unpack [_, tag] = tagEntry;
    if(tag === needle) return true;
  }
  return false;
}
module.exports.hasTag = hasTag;

//List all the tables that the table queries on.
function incomingTables(curTable) {
  var incoming = {};
  var queries = indexer.index("viewToQuery")[curTable];
  var queryToConstraint = indexer.index("queryToViewConstraint");
  var queryToAggregate = indexer.index("queryToAggregateConstraint");
  var constraints;
  foreach(query of queries) {
    constraints = queryToConstraint[query[0]];
    foreach(constraint of constraints) {
      incoming[constraint[2]] = true;
    }
    aggregates = queryToAggregate[query[0]];
    foreach(agg of aggregates) {
      incoming[agg[3]] = true;
    }
  }
  return Object.keys(incoming);
}
module.exports.incomingTables = incomingTables;

// List all the tables that query on this table.
function outgoingTables(curTable) {
  //@TODO
}
module.exports.outgoingTables = outgoingTables;

// List all derived workspace views.
function getNonInputWorkspaceViews() {
  var final = [];
  var views = indexer.facts("workspaceView");
  foreach(view of views) {
    if(!hasTag(view[0], "input")) {
      final.push(view[0]);
    }
  }
  return final;
}
module.exports.getNonInputWorkspaceViews = getNonInputWorkspaceViews;

// List the positions and sizes of each tile currently in the grid.
function getTileFootprints() {
  return indexer.facts("gridTile").map(function(cur, ix) {
    unpack [tile, type, w, h, x, y] = cur;
    return {pos: [x, y], size: [w, h]};
  });
}
module.exports.getTileFootprints = getTileFootprints;

function sortByIx(facts, ix) {
  return facts.sort(function(a, b) {
    return a[ix] - b[ix];
  });
};
module.exports.sortByIx = sortByIx;
<commit_msg>Adds dependency resolving diff remove helper<commit_after>import macros from "../macros.sjs";
var helpers = require("./helpers");

//---------------------------------------------------------
// Indexer
//---------------------------------------------------------

function Indexer(program, handlers) {
  this.worker = program.worker
  this.system = program.system;
  this.tableToIndexes = {};
  this.indexes = {};
  this.tablesToForward = [];
  this.handlers = handlers || {};
};
module.exports.Indexer = Indexer;

Indexer.prototype = {
  latestDiffs: {},
  handleDiffs: function(diffs, fromProgram) {
    this.latestDiffs = diffs;
    var tableToIndexes = this.tableToIndexes;
    var indexes = this.indexes;
    var system = this.system;
    var cur;
    var specialDiffs = ["view", "field"];
    var isSpecial = false;
    foreach(table of specialDiffs) {
      if(!diffs[table] || !(diffs[table].adds || diffs[table].removes)) { continue; }
      applyDiff(system, table, diffs[table]);
      isSpecial = true;
    }

    if(isSpecial) {
      var viewsToClear = getNonInputWorkspaceViews();

      // Nuke indexes before the system nukes facts.
      foreach(table of viewsToClear) {
        if(!tableToIndexes[table]) { continue; }
        var diff = {adds: [], removes: this.facts(table)};
        foreach(index of tableToIndexes[table]) {
          if(!index || !this.indexes[index]) { continue; }
          var cur = this.indexes[index];
          cur.indexer(cur.index, diff);
        }
      }

      system.recompile();
      //all non-input views were just cleared, make sure the worker clears storage
      //so that we end up with the views getting repopulated correctly.
      this.worker.postMessage({type: "clearStorage", views: viewsToClear})
    }

    forattr(table, diff of diffs) {
      if(tableToIndexes[table]) {
        foreach(index of tableToIndexes[table]) {
          cur = this.indexes[index];
          cur.index = cur.indexer(cur.index, diff);
        }
      }
      if(specialDiffs.indexOf(table) !== -1) { continue; }
      applyDiff(system, table, diff);
    }

    //we should only forward diffs to the program if they weren't
    //from the program to bgin with.
    if(!fromProgram) {
      var toSend = {};
      foreach(table of this.tablesToForward) {
        if(!diffs[table]) continue;
        toSend[table] = diffs[table];
      }
      if(Object.keys(toSend).length) {
        this.worker.postMessage({type: "diffs", diffs: toSend});
      }
    }

    //if we forced a recompile, we shouldn't redraw until the worker comes back
    //with the latest diffs.
    if(!isSpecial && this.handlers.diffsHandled) {
      this.handlers.diffsHandled(diffs);
    }
  },
  facts: function(table) {
    return this.system.getStore(table).getFacts();
  },
  index: function(index) {
    var cur = this.indexes[index];
    if(!cur) throw new Error("No index named: " + index);
    return cur.index;
  },
  hasIndex: function(index) {
    return !!this.indexes[index];
  },
  addIndex: function(table, name, indexer) {
    if(!this.tableToIndexes[table]) {
      this.tableToIndexes[table] = [];
    }
    this.tableToIndexes[table].push(name);
    //initialize the index by sending an add of all the facts we have now.
    this.indexes[name] = {index: indexer(null, {adds: this.facts(table), removes: []}),
                          indexer: indexer};
  },
  removeIndex: function(table, name) {
    var tableIndexes = this.tableToIndexes[table];
    var ix = tableIndexes.indexOf(name);
    if(ix !== -1) {
      tableIndexes.splice(ix, 1);
    }
    if(!tableIndexes.length) {
      delete this.tableToIndexes[table];
    }
    delete this.indexes[name];
  },
  forward: function(table) {
    if(!table) { return; }
    else if(typeof table === "object" && table.length) {
      this.tablesToForward.push.apply(this.tablesToForward, table);
    } else {
      this.tablesToForward.push(table);
    }
  },
  unforward: function(table) {
    var ix = this.tablesToForward.indexOf(table);
    if(ix !== -1) {
      this.tablesToForward.splice(ix, 1);
    }
  },
  first: function(table) {
    return this.facts(table)[0];
  },
  last: function(table) {
    var facts = this.facts(table);
    return facts[facts.length - 1];
  }
};

//---------------------------------------------------------
// Fact Indexers
//---------------------------------------------------------

var indexers = {
  // Builds a lookup table from index 1 to index 2. [Fact] -> {[Ix1]: Ix2}
  makeLookup: function(keyIx, valueIx) {
    if(valueIx !== false) {
      return function(cur, diffs) {
        var final = cur || {};
        foreach(remove of diffs.removes) {
          delete final[remove[keyIx]];
        }
        foreach(add of diffs.adds) {
          final[add[keyIx]] = add[valueIx];
        }
        return final;
      }
    } else {
      return function(cur, diffs) {
        var final = cur || {};
        foreach(remove of diffs.removes) {
          delete final[remove[keyIx]];
        }
        foreach(add of diffs.adds) {
          final[add[keyIx]] = add;
        }
        return final;
      }
    }
  },
  // Builds a lookup table from indexes 1 and 2 to index3. [Fact] -> {[Ix1]: {[Ix2]: Ix3}}
  makeLookup2D: function(key1Ix, key2Ix, valueIx) {
    return function(cur, diffs) {
      var final = cur || {};
      foreach(add of diffs.adds) {
        var key1 = add[key1Ix];
        if(!final[key1]) {
          final[key1] = {};
        }
        var key2 = add[key2Ix];
        final[key1][key2] = add[valueIx];
      }
      foreach(remove of diffs.removes) {
        var key1 = remove[key1Ix];
        if(!final[key1]) {
          continue;
        }
        var key2 = remove[key2Ix];
        delete final[key1][key2];
      }

      return final;
    };
  },
  // Groups facts by specified indexes, in order of hierarchy. [Fact] -> {[Any]: [Fact]|Group}
  makeCollector: function(keyIx) {
    if(arguments.length === 1) {
      return function(cur, diffs) {
        var final = cur || {};
        foreach(remove of diffs.removes) {
          if(!final[remove[keyIx]]) continue;
          final[remove[keyIx]] = final[remove[keyIx]].filter(function(cur) {
            return !arrayEqual(cur, remove)
          });
        }

        foreach(add of diffs.adds) {
          if(!final[add[keyIx]]) {
            final[add[keyIx]] = [];
          }
          final[add[keyIx]].push(add);
        }

        garbageCollectIndex(final);
        return final;
      }
    } else {
      var keyIxes = [].slice.apply(arguments);
      var lastKeyIx = keyIxes.pop();
      return function(cur, diffs) {
        var final = cur || {};
        foreach(add of diffs.adds) {
          var keys = [];
          foreach(ix, keyIx of keyIxes) {
            keys[ix] = add[keyIx];
          }
          var cur = helpers.aget(final, keys, true);
          if(!cur[add[lastKeyIx]]) {
            cur[add[lastKeyIx]] = [];
          }
          cur[add[lastKeyIx]].push(add);
        }
        foreach(remove of diffs.removes) {
          var keys = [];
          foreach(ix, keyIx of keyIxes) {
            keys[ix] = remove[keyIx];
          }
          var cur = helpers.aget(final, keys, false);
          if(!cur || !cur[remove[lastKeyIx]]) { continue; }
          cur[remove[lastKeyIx]] = cur[remove[lastKeyIx]].filter(function(c) {
            return !arrayEqual(c, remove);
          });

        }
        garbageCollectIndex(final);
        return final;
      }
    }
  },
  // Sorts facts by specified indexes, in order of priority. [Fact] -> [Fact]
  makeSorter: function() {
    var sortIxes = [].slice.apply(arguments);
    return function(cur, diffs) {
      var final = cur || [];
      foreach(remove of diffs.removes) {
        foreach(ix, item of final) {
          if(arrayEqual(item, remove)) {
            final.splice(ix, 1);
            break;
          }
        }
      }

      // @NOTE: This can be optimized further by presorting adds and maintaining loIx as a sliding window.
      foreach(add of diffs.adds) {
        var loIx = 0;
        var hiIx = final.length;
        foreach(sortIx of sortIxes) {
          for(var ix = loIx; ix < hiIx; ix++) {
            var item = final[ix];
            if(add[sortIx] > item[sortIx]) {
              loIx = ix + 1;
            } else if(add[sortIx] < item[sortIx]) {
              hiIx = ix;
              break;
            }
          }
        }
        final.splice(loIx, 0, add);
      }

      return final;
    }
  }
};
module.exports.indexers = indexers;

//---------------------------------------------------------
// Index helpers
//---------------------------------------------------------

// Delete any keys or descendant keys which are empty.
function garbageCollectIndex(index) {
  forattr(key, group of index) {
    if(group instanceof Array) {
      if(!group || !group.length) {
        delete index[key];
      }
    } else if(typeof group === "object") {
      garbageCollectIndex(group);
      if(!Object.keys(group).length) {
        delete index[key];
      }
    }
  }
}
module.exports.garbageCollectIndex = garbageCollectIndex;

function hasTag(id, needle) {
  var tags = indexer.index("idToTags")[id];
  foreach(tagEntry of tags) {
    unpack [_, tag] = tagEntry;
    if(tag === needle) return true;
  }
  return false;
}
module.exports.hasTag = hasTag;

//List all the tables that the table queries on.
function incomingTables(curTable) {
  var incoming = {};
  var queries = indexer.index("viewToQuery")[curTable];
  var queryToConstraint = indexer.index("queryToViewConstraint");
  var queryToAggregate = indexer.index("queryToAggregateConstraint");
  var constraints;
  foreach(query of queries) {
    constraints = queryToConstraint[query[0]];
    foreach(constraint of constraints) {
      incoming[constraint[2]] = true;
    }
    aggregates = queryToAggregate[query[0]];
    foreach(agg of aggregates) {
      incoming[agg[3]] = true;
    }
  }
  return Object.keys(incoming);
}
module.exports.incomingTables = incomingTables;

// List all the tables that query on this table.
function outgoingTables(curTable) {
  //@TODO
}
module.exports.outgoingTables = outgoingTables;

// List all derived workspace views.
function getNonInputWorkspaceViews() {
  var final = [];
  var views = indexer.facts("workspaceView");
  foreach(view of views) {
    if(!hasTag(view[0], "input")) {
      final.push(view[0]);
    }
  }
  return final;
}
module.exports.getNonInputWorkspaceViews = getNonInputWorkspaceViews;

// List the positions and sizes of each tile currently in the grid.
function getTileFootprints() {
  return indexer.facts("gridTile").map(function(cur, ix) {
    unpack [tile, type, w, h, x, y] = cur;
    return {pos: [x, y], size: [w, h]};
  });
}
module.exports.getTileFootprints = getTileFootprints;

function sortByIx(facts, ix) {
  return facts.sort(function(a, b) {
    return a[ix] - b[ix];
  });
};
module.exports.sortByIx = sortByIx;

//---------------------------------------------------------
// Diff helpers
//---------------------------------------------------------
var _dependencies = {
  view: {
    field: [0, 1],
    query: [0, 1],
    tag: [0, 0]
  },
  field: {
    aggregateConstraint: [0, 2],
    constantConstraint: [0, 1],
    displayName: [0, 0],
    functionConstraint: [0, 2],
    tag: [0, 0],
    viewConstraint: [0, 2]
  },
  query: {
    aggregateConstraint: [0, 1],
    constantConstraint: [0, 0],
    functionConstraint: [0, 1],
    tag: [0, 0],
    viewConstraint: [0, 1]
  },
  aggregateConstraint: {
    aggregateConstraintAggregateInput: [0, 0],
    aggregateConstraintBinding: [0, 0],
    aggregateConstraintSolverInput: [0, 0],
    tag: [0, 0]
  },
  functionConstraint: {
    functionConstraintInput: [0, 0],
    tag: [0, 0]
  },
  viewConstraint: {
    viewConstraintBinding: [0, 0],
    tag: [0, 0]
  }
};

function _collect(view, ix, val) {
  var facts = indexer.facts(view);
  var matches = [];
  if(!facts) { return matches; }

  foreach(fact of facts) {
    if(fact[ix] === val) {
      matches.push(fact);
    }
  }
  return matches;
}

var diff = {
// Remove fact from view, including all known dependencies.
  remove: function remove(view, fact) {
    return diff.removeAll(view, [fact]);
  },
  removeAll: function removeAll(view, facts, indent) {
    indent = indent || 0;
    var diff = {};
    if(!facts) { return diff; }
    foreach(fact of facts) {
      if(!fact) { continue; }
      if(!diff[view]) {
        diff[view] = {adds: [], removes: []};
      }
      diff[view].removes.push(fact);
      var deps = _dependencies[view];
      // console.log(new Array(indent + 1).join("> "), "Removing '" + view + "':", fact, "---");
      // console.log(new Array(indent + 2).join("  "), "X", view, diff[view]);
      if(!deps) { continue; }

      forattr(dep, keys of deps) {
        unpack [fromIx, toIx] = keys;
        // @FIXME: Slow fallback until we figure out how to integrate indexes.
        var depFacts = _collect(dep, toIx, fact[fromIx]);
        // console.log(new Array(indent + 2).join("  "), view, "<--", dep, "@", keys, ":", depFacts);
        helpers.merge(diff, removeAll(dep, depFacts, indent + 1));
      }
    }
    return diff;
  }
}
module.exports.diff = diff;
<|endoftext|>