<commit_before>// Copyright (c) 2012 Johannes Zellner webmaster@nebulon.de - All Rights Reserved

"use strict";

/*
 **************************************************
 * Singleton engine
 **************************************************
 *
 * Handles mainly toplevel elements and detects bindings.
 * This should contain as less as possible!
 *
 */

if (!Quick) {
    var Quick = {};
}

 // animation frame shim
window.requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        };
}());

// create main singleton object
if (!Quick.Engine) {
    Quick.Engine = (function () {
        var ret = {};
        var getterCalled = {};
        var _dirtyElements = {};

        ret.magicBindingState = false;
        ret.verbose = false;
        ret._elementIndex = 0;

        function log(msg, error) {
            if (ret.verbose || error) {
                console.log("[Quick.Engine] " + msg);
            }
        }

        // try to create a renderer backend, currently on DOM supported
        try {
            var renderer = new Quick.RendererDOM();
            ret.createElement = renderer.createElement;
            ret.addElement = renderer.addElement;
            ret.addElements = renderer.addElements;
            ret.renderElement = renderer.renderElement;
            ret.removeElement = renderer.removeElement;
        } catch (e) {
            log("Cannot create DOM renderer", true);
            ret.createElement = function () {};
            ret.addElements = function () {};
            ret.addElement = function () {};
            ret.renderElement = function () {};
            ret.removeElement = function () {};
        }

        // begin binding detection
        ret.enterMagicBindingState = function () {
            log("enterMagicBindingState");
            getterCalled = {};
            ret.magicBindingState = true;
        };

        // end binding detection
        ret.exitMagicBindingState = function () {
            log("exitMagicBindingState\n\n");
            ret.magicBindingState = false;
            return getterCalled;
        };

        ret.addCalledGetter = function (element, property) {
            getterCalled[element.id + "." + property] = { element: element, property: property };
        };

        // TODO should be part of the dom renderer?
        var fps = {};
        fps.d = new Date();
        fps.l = 0;

        function advance() {
            window.requestAnimFrame(advance);

            var i;
            for (i in _dirtyElements) {
                if (_dirtyElements.hasOwnProperty(i)) {
                    _dirtyElements[i].render();
                }
            }
            _dirtyElements = {};

            if (Quick.verbose) {
                if ((new Date() - fps.d) >= 1000) {
                    console.log("FPS: " + fps.l);
                    fps.d = new Date();
                    fps.l = 0;
                } else {
                    ++(fps.l);
                }
            }
        }

        advance();

        ret.dirty = function (element, property) {
            element._dirtyProperties[property] = true;
            if (!_dirtyElements[element._internalIndex]) {
                _dirtyElements[element._internalIndex] = element;
            }
        };

        return ret;
    }());
}

/*
 **************************************************
 * Basic Element
 **************************************************
 *
 * The main element, which handles its connections
 * and properties. It also calls into the renderer
 * by using render hooks.
 *
 */
Quick.Element = function (id, parent, typeHint) {
    this.id = id;
    this.typeHint = typeHint;
    this.parent = parent;

    if (typeHint !== "object") {
        this.element = Quick.Engine.createElement(typeHint, this);
    } else {
        this.element = null;
    }

    // internal use only
    this._internalIndex = Quick.Engine._elementIndex++;
    this._dirtyProperties = {};
    this._properties = {};
    this._connections = {};
    this._children = {};
    this._bound = {};

    if (this.parent) {
        this.parent.addChild(this);
    }
};

Quick.Element.prototype.children = function () {
    if (Quick.Engine.magicBindingState) {
        Quick.Engine.addCalledGetter(this, 'children');
    }

    return this._children;
};

// TODO both removes need to break the bindings for the children as well
Quick.Element.prototype.removeChild = function(child) {
    Quick.Engine.removeElement(child, this);
    delete this._children[child._internalIndex];

    this.emit("children");
};

Quick.Element.prototype.removeChildren = function () {
    var i;
    for (i in this._children) {
        if (this._children.hasOwnProperty(i)) {
            // TODO do we leak things here? elements are still referenced so maybe a delete?
            Quick.Engine.removeElement(this._children[i], this);
        }
    }

    this._children = {};

    this.emit("children");
};

Quick.Element.prototype.addChild = function (child) {
    // adds child id to the namespace
    this[child.id] = child;

    // adds the parents id to the child
    child[this.id] = this;

    // add child to siblings scope and vice versa
    var i;
    for (i in this._children) {
        if (this._children.hasOwnProperty(i)) {
            this._children[i][child.id] = child;
            child[this._children[i].id] = this._children[i];
        }
    }

    // add newly added child to internal children array
    this._children[child._internalIndex] = child;

    child.parent = this;
    Quick.Engine.addElement(child, this);
    this.emit("children");

    return child;
};

Quick.Element.prototype.render = function () {
    Quick.Engine.renderElement(this);
};

Quick.Element.prototype.addChanged = function (signal, callback) {
    if (!this._connections[signal]) {
        this._connections[signal] = [];
    }

    this._connections[signal][this._connections[signal].length] = callback;
    // console.log("connections for " + signal + " " + this._connections[signal].length);
};

Quick.Element.prototype.removeChanged = function (obj, signal) {
    var signalConnections = this._connections[signal];
    // check if there are any connections for this signal
    if (!signalConnections) {
        return;
    }

    for (var i = 0; i < signalConnections.length; ++i) {
        // TODO do implementation
    }
};

Quick.Element.prototype.addBinding = function (name, value) {
    var that = this;
    var hasBinding = false;

    // FIXME does not catch changing conditions in expression
    //  x: mouseArea.clicked ? a.y() : b:z();
    Quick.Engine.enterMagicBindingState();
    var val = value.apply(this);
    var getters = Quick.Engine.exitMagicBindingState();

    this.breakBindings(name);

    var bindingFunction = function() {
        that[name] = value.apply(that);
    };

    // store found bindings
    for (var getter in getters) {
        if (getters.hasOwnProperty(getter)) {
            var tmp = getters[getter];
            // store bindings to this for breaking
            this._bound[name][this._bound[name].length] = {
                element: tmp.element,
                property: tmp.property
            };

            tmp.element.addChanged(tmp.property, bindingFunction);
            hasBinding = true;
        }
    }

    return { hasBindings: hasBinding, value: val };
};

Quick.Element.prototype.addEventHandler = function (event, handler) {
    var that = this;
    var signal = event;

    if (signal === "" || typeof handler !== 'function') {
        return;
    }

    if (signal.indexOf('on') === 0) {
        signal = signal.slice(2);
    }

    this.addChanged(signal, function () {
        handler.apply(that);
    });
};

// Breaks all bindings assigned to this property
Quick.Element.prototype.breakBindings = function (name) {
    // break all previous bindings
    if (this._bound[name]) {
        for (var i = 0; i < this._bound[name].length; ++i) {
            this._bound[name][i].element.removeChanged(this, name);
        }
    }
    this._bound[name] = [];
};

// This allows to set the property without emit the change
// Does not break the binding!
Quick.Element.prototype.setSilent = function (name, value) {
    var setter = this.__lookupSetter__(name);
    if (typeof setter === 'function') {
        setter.call(this, value, true);
    }
};

// This breaks all previous bindings and adds a new binding
Quick.Element.prototype.set = function (name, value) {
    this.breakBindings(name);

    if (typeof value === 'function') {
        var ret = this.addBinding(name, value);
        if (ret.hasBindings) {
            this[name] = value;
        } else {
            this[name] = ret.value;
        }
    } else {
        this[name] = value;
    }
};

Quick.Element.prototype.addFunction = function (name, value) {
    this[name] = value;
};

Quick.Element.prototype.addProperty = function (name, value) {
    var that = this;
    var valueStore;

    // register property
    this._properties[name] = value;

    if (this.hasOwnProperty(name)) {
        this.name = value;
    } else {
        Object.defineProperty(this, name, {
            get: function () {
                // console.log("getter: ", that.id, name);
                if (Quick.Engine.magicBindingState)
                    Quick.Engine.addCalledGetter(that, name);

                if (typeof valueStore === 'function')
                    return valueStore.apply(that);

                return valueStore;
            },
            set: function (val, silent) {
                // console.log("setter: ", that.id, name, val);
                if (valueStore === val)
                    return;

                valueStore = val;

                // connections are called like the properties
                if (!silent)
                    that.emit(name);

                Quick.Engine.dirty(that, name);
            }
        });
    }
};

// initial set of all properties and binding evaluation
// should only be called once
Quick.Element.prototype.initializeBindings = function () {
    var name, i;
    for (name in this._properties) {
        if (this._properties.hasOwnProperty(name)) {
            var value = this._properties[name];

            // console.log("Element.initializeBindings()", this.id, name, value);

            // initial set and binding discovery
            if (typeof value === 'function') {
                var ret = this.addBinding(name, value);
                if (ret.hasBindings) {
                    this[name] = value;
                } else {
                    this[name] = ret.value;
                }
            } else {
                this[name] = value;
            }
        }
    }

    for (i in this._children) {
        if (this._children.hasOwnProperty(i)) {
            this._children[i].initializeBindings();
        }
    }

    // this calls the onload slot, if defined
    this.emit("load");
};

Quick.Element.prototype.emit = function (signal) {
    if (signal in this._connections) {
        var slots = this._connections[signal];
        for (var slot in slots) {
            if (slots.hasOwnProperty(slot)) {
                slots[slot]();
            }
        }
    }
};
<commit_msg>no need to check for ownProperty<commit_after>// Copyright (c) 2012 Johannes Zellner webmaster@nebulon.de - All Rights Reserved

"use strict";

/*
 **************************************************
 * Singleton engine
 **************************************************
 *
 * Handles mainly toplevel elements and detects bindings.
 * This should contain as less as possible!
 *
 */

if (!Quick) {
    var Quick = {};
}

 // animation frame shim
window.requestAnimFrame = (function () {
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        };
}());

// create main singleton object
if (!Quick.Engine) {
    Quick.Engine = (function () {
        var ret = {};
        var getterCalled = {};
        var _dirtyElements = {};

        ret.magicBindingState = false;
        ret.verbose = false;
        ret._elementIndex = 0;

        function log(msg, error) {
            if (ret.verbose || error) {
                console.log("[Quick.Engine] " + msg);
            }
        }

        // try to create a renderer backend, currently on DOM supported
        try {
            var renderer = new Quick.RendererDOM();
            ret.createElement = renderer.createElement;
            ret.addElement = renderer.addElement;
            ret.addElements = renderer.addElements;
            ret.renderElement = renderer.renderElement;
            ret.removeElement = renderer.removeElement;
        } catch (e) {
            log("Cannot create DOM renderer", true);
            ret.createElement = function () {};
            ret.addElements = function () {};
            ret.addElement = function () {};
            ret.renderElement = function () {};
            ret.removeElement = function () {};
        }

        // begin binding detection
        ret.enterMagicBindingState = function () {
            log("enterMagicBindingState");
            getterCalled = {};
            ret.magicBindingState = true;
        };

        // end binding detection
        ret.exitMagicBindingState = function () {
            log("exitMagicBindingState\n\n");
            ret.magicBindingState = false;
            return getterCalled;
        };

        ret.addCalledGetter = function (element, property) {
            getterCalled[element.id + "." + property] = { element: element, property: property };
        };

        // TODO should be part of the dom renderer?
        var fps = {};
        fps.d = new Date();
        fps.l = 0;

        function advance() {
            window.requestAnimFrame(advance);

            for (var i in _dirtyElements) {
                _dirtyElements[i].render();
            }
            _dirtyElements = {};

            if (Quick.verbose) {
                if ((new Date() - fps.d) >= 2000) {
                    console.log("FPS: " + fps.l / 2.0);
                    fps.d = new Date();
                    fps.l = 0;
                } else {
                    ++(fps.l);
                }
            }
        }

        advance();

        ret.dirty = function (element, property) {
            element._dirtyProperties[property] = true;
            if (!_dirtyElements[element._internalIndex]) {
                _dirtyElements[element._internalIndex] = element;
            }
        };

        return ret;
    }());
}

/*
 **************************************************
 * Basic Element
 **************************************************
 *
 * The main element, which handles its connections
 * and properties. It also calls into the renderer
 * by using render hooks.
 *
 */
Quick.Element = function (id, parent, typeHint) {
    this.id = id;
    this.typeHint = typeHint;
    this.parent = parent;

    if (typeHint !== "object") {
        this.element = Quick.Engine.createElement(typeHint, this);
    } else {
        this.element = null;
    }

    // internal use only
    this._internalIndex = Quick.Engine._elementIndex++;
    this._dirtyProperties = {};
    this._properties = {};
    this._connections = {};
    this._children = {};
    this._bound = {};

    if (this.parent) {
        this.parent.addChild(this);
    }
};

Quick.Element.prototype.children = function () {
    if (Quick.Engine.magicBindingState) {
        Quick.Engine.addCalledGetter(this, 'children');
    }

    return this._children;
};

// TODO both removes need to break the bindings for the children as well
Quick.Element.prototype.removeChild = function(child) {
    Quick.Engine.removeElement(child, this);
    delete this._children[child._internalIndex];

    this.emit("children");
};

Quick.Element.prototype.removeChildren = function () {
    var i;
    for (i in this._children) {
        if (this._children.hasOwnProperty(i)) {
            // TODO do we leak things here? elements are still referenced so maybe a delete?
            Quick.Engine.removeElement(this._children[i], this);
        }
    }

    this._children = {};

    this.emit("children");
};

Quick.Element.prototype.addChild = function (child) {
    // adds child id to the namespace
    this[child.id] = child;

    // adds the parents id to the child
    child[this.id] = this;

    // add child to siblings scope and vice versa
    var i;
    for (i in this._children) {
        if (this._children.hasOwnProperty(i)) {
            this._children[i][child.id] = child;
            child[this._children[i].id] = this._children[i];
        }
    }

    // add newly added child to internal children array
    this._children[child._internalIndex] = child;

    child.parent = this;
    Quick.Engine.addElement(child, this);
    this.emit("children");

    return child;
};

Quick.Element.prototype.render = function () {
    Quick.Engine.renderElement(this);
};

Quick.Element.prototype.addChanged = function (signal, callback) {
    if (!this._connections[signal]) {
        this._connections[signal] = [];
    }

    this._connections[signal][this._connections[signal].length] = callback;
    // console.log("connections for " + signal + " " + this._connections[signal].length);
};

Quick.Element.prototype.removeChanged = function (obj, signal) {
    var signalConnections = this._connections[signal];
    // check if there are any connections for this signal
    if (!signalConnections) {
        return;
    }

    for (var i = 0; i < signalConnections.length; ++i) {
        // TODO do implementation
    }
};

Quick.Element.prototype.addBinding = function (name, value) {
    var that = this;
    var hasBinding = false;

    // FIXME does not catch changing conditions in expression
    //  x: mouseArea.clicked ? a.y() : b:z();
    Quick.Engine.enterMagicBindingState();
    var val = value.apply(this);
    var getters = Quick.Engine.exitMagicBindingState();

    this.breakBindings(name);

    var bindingFunction = function() {
        that[name] = value.apply(that);
    };

    // store found bindings
    for (var getter in getters) {
        if (getters.hasOwnProperty(getter)) {
            var tmp = getters[getter];
            // store bindings to this for breaking
            this._bound[name][this._bound[name].length] = {
                element: tmp.element,
                property: tmp.property
            };

            tmp.element.addChanged(tmp.property, bindingFunction);
            hasBinding = true;
        }
    }

    return { hasBindings: hasBinding, value: val };
};

Quick.Element.prototype.addEventHandler = function (event, handler) {
    var that = this;
    var signal = event;

    if (signal === "" || typeof handler !== 'function') {
        return;
    }

    if (signal.indexOf('on') === 0) {
        signal = signal.slice(2);
    }

    this.addChanged(signal, function () {
        handler.apply(that);
    });
};

// Breaks all bindings assigned to this property
Quick.Element.prototype.breakBindings = function (name) {
    // break all previous bindings
    if (this._bound[name]) {
        for (var i = 0; i < this._bound[name].length; ++i) {
            this._bound[name][i].element.removeChanged(this, name);
        }
    }
    this._bound[name] = [];
};

// This allows to set the property without emit the change
// Does not break the binding!
Quick.Element.prototype.setSilent = function (name, value) {
    var setter = this.__lookupSetter__(name);
    if (typeof setter === 'function') {
        setter.call(this, value, true);
    }
};

// This breaks all previous bindings and adds a new binding
Quick.Element.prototype.set = function (name, value) {
    this.breakBindings(name);

    if (typeof value === 'function') {
        var ret = this.addBinding(name, value);
        if (ret.hasBindings) {
            this[name] = value;
        } else {
            this[name] = ret.value;
        }
    } else {
        this[name] = value;
    }
};

Quick.Element.prototype.addFunction = function (name, value) {
    this[name] = value;
};

Quick.Element.prototype.addProperty = function (name, value) {
    var that = this;
    var valueStore;

    // register property
    this._properties[name] = value;

    if (this.hasOwnProperty(name)) {
        this.name = value;
    } else {
        Object.defineProperty(this, name, {
            get: function () {
                // console.log("getter: ", that.id, name);
                if (Quick.Engine.magicBindingState)
                    Quick.Engine.addCalledGetter(that, name);

                if (typeof valueStore === 'function')
                    return valueStore.apply(that);

                return valueStore;
            },
            set: function (val, silent) {
                // console.log("setter: ", that.id, name, val);
                if (valueStore === val)
                    return;

                valueStore = val;

                // connections are called like the properties
                if (!silent)
                    that.emit(name);

                Quick.Engine.dirty(that, name);
            }
        });
    }
};

// initial set of all properties and binding evaluation
// should only be called once
Quick.Element.prototype.initializeBindings = function () {
    var name, i;
    for (name in this._properties) {
        if (this._properties.hasOwnProperty(name)) {
            var value = this._properties[name];

            // console.log("Element.initializeBindings()", this.id, name, value);

            // initial set and binding discovery
            if (typeof value === 'function') {
                var ret = this.addBinding(name, value);
                if (ret.hasBindings) {
                    this[name] = value;
                } else {
                    this[name] = ret.value;
                }
            } else {
                this[name] = value;
            }
        }
    }

    for (i in this._children) {
        if (this._children.hasOwnProperty(i)) {
            this._children[i].initializeBindings();
        }
    }

    // this calls the onload slot, if defined
    this.emit("load");
};

Quick.Element.prototype.emit = function (signal) {
    if (signal in this._connections) {
        var slots = this._connections[signal];
        for (var slot in slots) {
            if (slots.hasOwnProperty(slot)) {
                slots[slot]();
            }
        }
    }
};
<|endoftext|>


<commit_before>jQuery(function ($) {
    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new jQuery.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         *
         */
        callRemote: function () {
            var el      = $(this),
                data    = [],
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href');

            if (el.context.tagName.toUpperCase() === 'FORM') {
                data = el.serializeArray();
            }

            // TODO: should let the developer know no url was found
            if (url !== undefined) {
                if (el.triggerAndReturn('ajax:before')) {
                    $.ajax({
                        url: url,
                        data: data,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader("Accept", "text/javascript");
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote="true"]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote="true"],input[data-remote="true"]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    /**
     * disable_with handlers
     */
    $('form[data-remote="true"]').live('ajax:before', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.data('enable_with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $('form[data-remote="true"]').live('ajax:after', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.removeAttr('disabled')
                 .val(input.data('enable_with'));
        });
    });
});
<commit_msg>fix link_to :method => :delete, with no ajax<commit_after>jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new jQuery.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         *
         */
        callRemote: function () {
            var el      = $(this),
                data    = [],
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href');

            if (el.context.tagName.toUpperCase() === 'FORM') {
                data = el.serializeArray();
            }

            // TODO: should let the developer know no url was found
            if (url !== undefined) {
                if (el.triggerAndReturn('ajax:before')) {
                    $.ajax({
                        url: url,
                        data: data,
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader("Accept", "text/javascript");
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote="true"]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote="true"],input[data-remote="true"]').live('click', function (e) {
      $(this).callRemote();
      e.preventDefault();
    });

    $('a[data-method][data-remote!=true]').live('click',function(e){
      var link = $(this),
        href = link.attr('href'),
        method = link.attr('data-method'),
        form = $('<form method="post" action="'+href+'">'),
        input = $('<input name="_method" value="'+method+'" type="hidden" />'),
        csrf_input = $('<input name="authenticity_token" value="'+csrf_token+'" type="hidden" />');

      form.hide();
      form.append(input);
      form.append(csrf_input);

      $('body').append(form); // redundant?

      e.preventDefault();
      form.submit();
    });

    /**
     * disable_with handlers
     */
    $('form[data-remote="true"]').live('ajax:before', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.data('enable_with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $('form[data-remote="true"]').live('ajax:after', function () {
        $(this).children('input[data-disable-with]').each(function (i, el) {
            var input = $(el);
            input.removeAttr('disabled')
                 .val(input.data('enable_with'));
        });
    });
});

<|endoftext|>


<commit_before>jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         */
        callRemote: function () {
            var el      = this,
                data    = el.is('form') ? el.serializeArray() : [],
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href');

            if (url === undefined) {
              throw "No URL specified for remote call (action or href must be present).";
            } else {
                if (el.triggerAndReturn('ajax:before')) {
                    $.ajax({
                        url: url,
                        data: data,
                        dataType: 'script',
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote],input[data-remote]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-method]:not([data-remote])').live('click', function (e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'"></form>'),
            metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

        if (csrf_param != null && csrf_token != null) {
          metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
        }

        form.hide()
            .append(metadata_input)
            .appendTo('body');

        e.preventDefault();
        form.submit();
    });

    /**
     * disable-with handlers
     */
    var disable_with_input_selector = 'input[data-disable-with]';
    var disable_with_form_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')';

    $(disable_with_form_selector).live('ajax:before', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.data('enable-with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $(disable_with_form_selector).live('ajax:after', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.removeAttr('disabled')
                 .val(input.data('enable-with'));
        });
    });
});

<commit_msg>disable_with is enabled too soon closes #6<commit_after>jQuery(function ($) {
    var csrf_token = $('meta[name=csrf-token]').attr('content'),
        csrf_param = $('meta[name=csrf-param]').attr('content');

    $.fn.extend({
        /**
         * Triggers a custom event on an element and returns the event result
         * this is used to get around not being able to ensure callbacks are placed
         * at the end of the chain.
         *
         * TODO: deprecate with jQuery 1.4.2 release, in favor of subscribing to our
         *       own events and placing ourselves at the end of the chain.
         */
        triggerAndReturn: function (name, data) {
            var event = new $.Event(name);
            this.trigger(event, data);

            return event.result !== false;
        },

        /**
         * Handles execution of remote calls firing overridable events along the way
         */
        callRemote: function () {
            var el      = this,
                data    = el.is('form') ? el.serializeArray() : [],
                method  = el.attr('method') || el.attr('data-method') || 'GET',
                url     = el.attr('action') || el.attr('href');

            if (url === undefined) {
              throw "No URL specified for remote call (action or href must be present).";
            } else {
                if (el.triggerAndReturn('ajax:before')) {
                    $.ajax({
                        url: url,
                        data: data,
                        dataType: 'script',
                        type: method.toUpperCase(),
                        beforeSend: function (xhr) {
                            el.trigger('ajax:loading', xhr);
                        },
                        success: function (data, status, xhr) {
                            el.trigger('ajax:success', [data, status, xhr]);
                        },
                        complete: function (xhr) {
                            el.trigger('ajax:complete', xhr);
                        },
                        error: function (xhr, status, error) {
                            el.trigger('ajax:failure', [xhr, status, error]);
                        }
                    });
                }

                el.trigger('ajax:after');
            }
        }
    });

    /**
     *  confirmation handler
     */
    $('a[data-confirm],input[data-confirm]').live('click', function () {
        var el = $(this);
        if (el.triggerAndReturn('confirm')) {
            if (!confirm(el.attr('data-confirm'))) {
                return false;
            }
        }
    });

    /**
     * remote handlers
     */
    $('form[data-remote]').live('submit', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-remote],input[data-remote]').live('click', function (e) {
        $(this).callRemote();
        e.preventDefault();
    });

    $('a[data-method]:not([data-remote])').live('click', function (e){
        var link = $(this),
            href = link.attr('href'),
            method = link.attr('data-method'),
            form = $('<form method="post" action="'+href+'"></form>'),
            metadata_input = '<input name="_method" value="'+method+'" type="hidden" />';

        if (csrf_param != null && csrf_token != null) {
          metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />';
        }

        form.hide()
            .append(metadata_input)
            .appendTo('body');

        e.preventDefault();
        form.submit();
    });

    /**
     * disable-with handlers
     */
    var disable_with_input_selector = 'input[data-disable-with]';
    var disable_with_form_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')';

    $(disable_with_form_selector).live('ajax:before', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.data('enable-with', input.val())
                 .attr('value', input.attr('data-disable-with'))
                 .attr('disabled', 'disabled');
        });
    });

    $(disable_with_form_selector).live('ajax:complete', function () {
        $(this).find(disable_with_input_selector).each(function () {
            var input = $(this);
            input.removeAttr('disabled')
                 .val(input.data('enable-with'));
        });
    });
});
<|endoftext|>


<commit_before>/* French initialisation for the jQuery UI date picker plugin. */
/* Written by Keith Wood (kbwood{at}iinet.com.au),
			  Stéphane Nahmani (sholby@sholby.net),
			  Stéphane Raimbault <stephane.raimbault@gmail.com> */
(function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define([ "../datepicker" ], factory );
	} else {

		// Browser globals
		factory( jQuery.datepicker );
	}
}(function( datepicker ) {

datepicker.regional['fr'] = {
	closeText: 'Fermer',
	prevText: 'Précédent',
	nextText: 'Suivant',
	currentText: 'Aujourd\'hui',
	monthNames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
		'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
	monthNamesShort: ['janv.', 'févr.', 'mars', 'avril', 'mai', 'juin',
		'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
	dayNames: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
	dayNamesShort: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
	dayNamesMin: ['D','L','M','M','J','V','S'],
	weekHeader: 'Sem.',
	dateFormat: 'dd/mm/yy',
	firstDay: 1,
	isRTL: false,
	showMonthAfterYear: false,
	yearSuffix: ''};
datepicker.setDefaults(datepicker.regional['fr']);

return datepicker.regional['fr'];

}));
<commit_msg>Datepicker: Fix French locale<commit_after>/* French initialisation for the jQuery UI date picker plugin. */
/* Written by Keith Wood (kbwood{at}iinet.com.au),
			  Stéphane Nahmani (sholby@sholby.net),
			  Stéphane Raimbault <stephane.raimbault@gmail.com> */
(function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define([ "../datepicker" ], factory );
	} else {

		// Browser globals
		factory( jQuery.datepicker );
	}
}(function( datepicker ) {

datepicker.regional['fr'] = {
	closeText: 'Fermer',
	prevText: 'Précédent',
	nextText: 'Suivant',
	currentText: 'Aujourd\'hui',
	monthNames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
		'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
	monthNamesShort: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin',
		'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
	dayNames: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
	dayNamesShort: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
	dayNamesMin: ['D','L','M','M','J','V','S'],
	weekHeader: 'Sem.',
	dateFormat: 'dd/mm/yy',
	firstDay: 1,
	isRTL: false,
	showMonthAfterYear: false,
	yearSuffix: ''};
datepicker.setDefaults(datepicker.regional['fr']);

return datepicker.regional['fr'];

}));
<|endoftext|>


<commit_before>//scene.js
//Scene Object management and rendering

(function () {
  //Define some locals
  var Vec3 = PhiloGL.Vec3,
      Mat4 = PhiloGL.Mat4;

  //Scene class
  var Scene = function(program, camera, opt) {
    opt = $.merge({
      lights: {
        enable: false,
        //ambient light
        ambient: {
          r: 0.2,
          g: 0.2,
          b: 0.2
        },
        //directional light
        directional: {
          direction: {
            x: 1,
            y: 1,
            z: 1
          },
          color: {
            r: 0,
            g: 0,
            b: 0
          }
        }
        //point light
        //points: []
      },
      effects: {
        fog: false
        // { near, far, color }
      }
    }, opt || {});

    this.program = program;
    this.camera = camera;
    this.models = [];
    this.config = opt;

    this.o3dHash = {};
    this.setupPicking();
  };

  Scene.prototype = {

    add: function() {
      for (var i = 0, models = this.models, l = arguments.length; i < l; i++) {
        var model = arguments[i];
        //Generate unique id for model
        model.id = model.id || Scene.id++;
        models.push(model);
        //Create and load Buffers
        this.defineBuffers(model);
      }
    },

    defineBuffers: function(obj) {
      var program = this.program;

      obj.setVertices(program, true);
      obj.setColors(program, true);
      obj.setNormals(program, true);
      //obj.setTextures(program, true);
      obj.setTexCoords(program, true);
      obj.setIndices(program, true);
    },

    beforeRender: function() {
      this.setupLighting();
      this.setupEffects();
      //Set Camera view and projection matrix
      var camera = this.camera,
          program = this.program;
      program.setUniform('projectionMatrix', camera.projection);
      program.setUniform('viewMatrix', camera.modelView);
    },

    //Setup the lighting system: ambient, directional, point lights.
    setupLighting: function() {
      //Setup Lighting
      var abs = Math.abs,
          program = this.program,
          camera = this.camera,
          cpos = camera.position,
          light = this.config.lights,
          ambient = light.ambient,
          directional = light.directional,
          dcolor = directional.color,
          dir = directional.direction,
          enable = light.enable,
          points = light.points && $.splat(light.points) || [];

      //Normalize lighting direction vector
      var newDir = Vec3.clone(dir);
      Vec3.$unit(newDir);
      Vec3.$scale(newDir, -1);
      dir = newDir;

      //Set light uniforms. Ambient and directional lights.
      program.setUniform('enableLights', enable);
      if (light.enable) {
        program.setUniform('ambientColor', [ambient.r, ambient.g, ambient.b]);
        program.setUniform('directionalColor', [dcolor.r, dcolor.g, dcolor.b]);
        program.setUniform('lightingDirection', [dir.x, dir.y, dir.z]);
      }

      //Set point lights
      program.setUniform('enableSpecularHighlights', false);
      for (var i = 0, l = Scene.MAX_POINT_LIGHTS, pl = points.length; i < l; i++) {
        var index = i + 1;
        if (enable && i < pl) {
          var point = points[i],
              position = point.position,
              color = point.color || point.diffuse,
              spec = point.specular;
          program.setUniform('enablePoint' + index, true);
          program.setUniform('pointLocation' + index, [position.x, position.y, position.z]);
          program.setUniform('pointColor' + index, [color.r, color.g, color.b]);
          //Add specular color and enableSpecularHighlights
          if (spec) {
            program.setUniform('enableSpecularHighlights', true);
            program.setUniform('pointSpecularColor' + index, [spec.r, spec.g, spec.b]);
          }
        } else {
          program.setUniform('enablePoint' + index, false);
        }
      }
    },

    //Setup effects like fog, etc.
    setupEffects: function() {
      var program = this.program,
          config = this.config.effects,
          fog = config.fog,
          color = fog.color || { r: 0.5, g: 0.5, b: 0.5 };

      if (fog) {
        program.setUniforms({
          'hasFog': true,
          'fogNear': fog.near,
          'fogFar': fog.far,
          'fogColor': [color.r, color.g, color.b]
        });
      } else {
        program.setUniform('hasFog', false);
      }
    },

    //Renders all objects in the scene.
    render: function(opt) {
      var program = this.program,
          camera = this.camera,
          options = $.merge({
            onBeforeRender: $.empty,
            onAfterRender: $.empty
          }, opt || {});

      this.beforeRender();
      this.models.forEach(function(elem, i) {
        elem.onBeforeRender(program, camera);
        options.onBeforeRender(elem, i);
        this.renderObject(elem);
        options.onAfterRender(elem, i);
        elem.onAfterRender(program, camera);
      }, this);
    },

    renderToTexture: function(name, opt) {
      var program = this.program,
          texture = program.textures[name + '-texture'],
          texMemo = program.textureMemo[name + '-texture'];

      this.render(opt);

      gl.bindTexture(texMemo.textureType, texture);
      gl.generateMipmap(texMemo.textureType);
      gl.bindTexture(texMemo.textureType, null);
    },

    renderObject: function(obj) {
      var program = this.program,
          camera = this.camera,
          view = new Mat4;

      obj.setUniforms(program);
      obj.setShininess(program);
      obj.setVertices(program);
      obj.setColors(program);
      obj.setNormals(program);
      obj.setTextures(program);
      obj.setTexCoords(program);
      obj.setIndices(program);

      //Now set modelView and normal matrices
      view.mulMat42(camera.modelView, obj.matrix);
      program.setUniform('modelViewMatrix', view);
      program.setUniform('normalMatrix', view.invert().$transpose());

      //Draw
      //TODO(nico): move this into O3D, but, somehow, abstract the gl.draw* methods inside that object.
      if (obj.render) {
        obj.render(gl, program, camera);
      } else {
        if (obj.indices) {
          gl.drawElements((obj.drawType !== undefined)? gl.get(obj.drawType) : gl.TRIANGLES, obj.indices.length, gl.UNSIGNED_SHORT, 0);
        } else {
          gl.drawArrays(gl.get(obj.drawType || 'TRIANGLES'), 0, obj.toFloat32Array('vertices').length / 3);
        }
      }
    },

    //setup picking framebuffer
    setupPicking: function() {
      var canvas = gl.canvas,
          program = this.program;
      //create framebuffer
      program.setFrameBuffer('$picking', {
        width: 1,
        height: 1,
        bindToTexture: {
          parameters: [{
            name: 'TEXTURE_MAG_FILTER',
            value: 'LINEAR'
          }, {
            name: 'TEXTURE_MIN_FILTER',
            value: 'LINEAR',
            generateMipmap: false
          }]
        },
        bindToRenderBuffer: true
      });
      program.setFrameBuffer('$picking', false);
    },

    //returns an element at the given position
    pick: function(x, y) {
      var o3dHash = this.o3dHash,
          program = this.program,
          camera = this.camera,
          config = this.config,
          memoLightEnable = config.lights.enable,
          memoFog = config.effects.fog,
          width = gl.canvas.width,
          height = gl.canvas.height,
          hash = [],
          delay = 200,
          now = $.time(),
          last = this.last || 0;

      //setup the scene for picking
      config.lights.enable = false;
      config.effects.fog = false;

      //enable picking and render to texture
      program.setUniform('enablePicking', true);
      program.setFrameBuffer('$picking', true);

      //render the scene to a texture
      o3dHash = this.o3dHash = {};
      gl.disable(gl.BLEND);
      gl.viewport(-x, y - height, width, height);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      this.renderToTexture('$picking', {
        onBeforeRender: function(elem, i) {
          var suc = i + 1;
          hash[0] = suc % 256;
          hash[1] = ((suc / 256) >> 0) % 256;
          hash[2] = ((suc / (256 * 256)) >> 0) % 256;
          program.setUniform('pickColor', [hash[0] / 255, hash[1] / 255, hash[2] / 255]);
          o3dHash[String(hash)] = elem;
        }
      });
      this.last = $.time();

      //grab the color of the pointed pixel in the texture
      var pixels = new Uint8Array(1 * 1 * 4);
      gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
      var elem = o3dHash[String([pixels[0], pixels[1], pixels[2]])];

      //restore all values
      program.setFrameBuffer('$picking', false);
      program.setUniform('enablePicking', false);
      config.lights.enable = memoLightEnable;
      config.effects.fog = memoFog;

      return elem;
    }
  };

  Scene.id = $.time();

  Scene.MAX_TEXTURES = 3;
  Scene.MAX_POINT_LIGHTS = 3;

  PhiloGL.Scene = Scene;

})();
<commit_msg>Some refactor.<commit_after>//scene.js
//Scene Object management and rendering

(function () {
  //Define some locals
  var Vec3 = PhiloGL.Vec3,
      Mat4 = PhiloGL.Mat4;

  //Scene class
  var Scene = function(program, camera, opt) {
    opt = $.merge({
      lights: {
        enable: false,
        //ambient light
        ambient: {
          r: 0.2,
          g: 0.2,
          b: 0.2
        },
        //directional light
        directional: {
          direction: {
            x: 1,
            y: 1,
            z: 1
          },
          color: {
            r: 0,
            g: 0,
            b: 0
          }
        }
        //point light
        //points: []
      },
      effects: {
        fog: false
        // { near, far, color }
      }
    }, opt || {});

    this.program = program;
    this.camera = camera;
    this.models = [];
    this.config = opt;

    this.setupPicking();
  };

  Scene.prototype = {

    add: function() {
      for (var i = 0, models = this.models, l = arguments.length; i < l; i++) {
        var model = arguments[i];
        //Generate unique id for model
        model.id = model.id || Scene.id++;
        models.push(model);
        //Create and load Buffers
        this.defineBuffers(model);
      }
    },

    defineBuffers: function(obj) {
      var program = this.program;

      obj.setVertices(program, true);
      obj.setColors(program, true);
      obj.setNormals(program, true);
      //obj.setTextures(program, true);
      obj.setTexCoords(program, true);
      obj.setIndices(program, true);
    },

    beforeRender: function() {
      this.setupLighting();
      this.setupEffects();
      //Set Camera view and projection matrix
      var camera = this.camera,
          program = this.program;
      program.setUniform('projectionMatrix', camera.projection);
      program.setUniform('viewMatrix', camera.modelView);
    },

    //Setup the lighting system: ambient, directional, point lights.
    setupLighting: function() {
      //Setup Lighting
      var abs = Math.abs,
          program = this.program,
          camera = this.camera,
          cpos = camera.position,
          light = this.config.lights,
          ambient = light.ambient,
          directional = light.directional,
          dcolor = directional.color,
          dir = directional.direction,
          enable = light.enable,
          points = light.points && $.splat(light.points) || [];

      //Normalize lighting direction vector
      var newDir = Vec3.clone(dir);
      Vec3.$unit(newDir);
      Vec3.$scale(newDir, -1);
      dir = newDir;

      //Set light uniforms. Ambient and directional lights.
      program.setUniform('enableLights', enable);
      if (light.enable) {
        program.setUniform('ambientColor', [ambient.r, ambient.g, ambient.b]);
        program.setUniform('directionalColor', [dcolor.r, dcolor.g, dcolor.b]);
        program.setUniform('lightingDirection', [dir.x, dir.y, dir.z]);
      }

      //Set point lights
      program.setUniform('enableSpecularHighlights', false);
      for (var i = 0, l = Scene.MAX_POINT_LIGHTS, pl = points.length; i < l; i++) {
        var index = i + 1;
        if (enable && i < pl) {
          var point = points[i],
              position = point.position,
              color = point.color || point.diffuse,
              spec = point.specular;
          program.setUniform('enablePoint' + index, true);
          program.setUniform('pointLocation' + index, [position.x, position.y, position.z]);
          program.setUniform('pointColor' + index, [color.r, color.g, color.b]);
          //Add specular color and enableSpecularHighlights
          if (spec) {
            program.setUniform('enableSpecularHighlights', true);
            program.setUniform('pointSpecularColor' + index, [spec.r, spec.g, spec.b]);
          }
        } else {
          program.setUniform('enablePoint' + index, false);
        }
      }
    },

    //Setup effects like fog, etc.
    setupEffects: function() {
      var program = this.program,
          config = this.config.effects,
          fog = config.fog,
          color = fog.color || { r: 0.5, g: 0.5, b: 0.5 };

      if (fog) {
        program.setUniforms({
          'hasFog': true,
          'fogNear': fog.near,
          'fogFar': fog.far,
          'fogColor': [color.r, color.g, color.b]
        });
      } else {
        program.setUniform('hasFog', false);
      }
    },

    //Renders all objects in the scene.
    render: function(opt) {
      var program = this.program,
          camera = this.camera,
          options = $.merge({
            onBeforeRender: $.empty,
            onAfterRender: $.empty
          }, opt || {});

      this.beforeRender();
      this.models.forEach(function(elem, i) {
        elem.onBeforeRender(program, camera);
        options.onBeforeRender(elem, i);
        this.renderObject(elem);
        options.onAfterRender(elem, i);
        elem.onAfterRender(program, camera);
      }, this);
    },

    renderToTexture: function(name, opt) {
      var program = this.program,
          texture = program.textures[name + '-texture'],
          texMemo = program.textureMemo[name + '-texture'];

      this.render(opt);

      gl.bindTexture(texMemo.textureType, texture);
      gl.generateMipmap(texMemo.textureType);
      gl.bindTexture(texMemo.textureType, null);
    },

    renderObject: function(obj) {
      var program = this.program,
          camera = this.camera,
          view = new Mat4;

      obj.setUniforms(program);
      obj.setShininess(program);
      obj.setVertices(program);
      obj.setColors(program);
      obj.setNormals(program);
      obj.setTextures(program);
      obj.setTexCoords(program);
      obj.setIndices(program);

      //Now set modelView and normal matrices
      view.mulMat42(camera.modelView, obj.matrix);
      program.setUniform('modelViewMatrix', view);
      program.setUniform('normalMatrix', view.invert().$transpose());

      //Draw
      //TODO(nico): move this into O3D, but, somehow, abstract the gl.draw* methods inside that object.
      if (obj.render) {
        obj.render(gl, program, camera);
      } else {
        if (obj.indices) {
          gl.drawElements((obj.drawType !== undefined)? gl.get(obj.drawType) : gl.TRIANGLES, obj.indices.length, gl.UNSIGNED_SHORT, 0);
        } else {
          gl.drawArrays(gl.get(obj.drawType || 'TRIANGLES'), 0, obj.toFloat32Array('vertices').length / 3);
        }
      }
    },

    //setup picking framebuffer
    setupPicking: function() {
      var canvas = gl.canvas,
          program = this.program;
      //create framebuffer
      program.setFrameBuffer('$picking', {
        width: 1,
        height: 1,
        bindToTexture: {
          parameters: [{
            name: 'TEXTURE_MAG_FILTER',
            value: 'LINEAR'
          }, {
            name: 'TEXTURE_MIN_FILTER',
            value: 'LINEAR',
            generateMipmap: false
          }]
        },
        bindToRenderBuffer: true
      });
      program.setFrameBuffer('$picking', false);
    },

    //returns an element at the given position
    pick: function(x, y) {
      var o3dHash = {},
          program = this.program,
          camera = this.camera,
          config = this.config,
          memoLightEnable = config.lights.enable,
          memoFog = config.effects.fog,
          width = gl.canvas.width,
          height = gl.canvas.height,
          hash = [],
          delay = 200,
          now = $.time(),
          last = this.last || 0;

      //setup the scene for picking
      config.lights.enable = false;
      config.effects.fog = false;

      //enable picking and render to texture
      program.setUniform('enablePicking', true);
      program.setFrameBuffer('$picking', true);

      //render the scene to a texture
      gl.disable(gl.BLEND);
      gl.viewport(-x, y - height, width, height);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
      this.renderToTexture('$picking', {
        onBeforeRender: function(elem, i) {
          var suc = i + 1;
          hash[0] = suc % 256;
          hash[1] = ((suc / 256) >> 0) % 256;
          hash[2] = ((suc / (256 * 256)) >> 0) % 256;
          program.setUniform('pickColor', [hash[0] / 255, hash[1] / 255, hash[2] / 255]);
          o3dHash[String(hash)] = elem;
        }
      });
      this.last = $.time();

      //grab the color of the pointed pixel in the texture
      var pixels = new Uint8Array(1 * 1 * 4);
      gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
      var elem = o3dHash[String([pixels[0], pixels[1], pixels[2]])];

      //restore all values
      program.setFrameBuffer('$picking', false);
      program.setUniform('enablePicking', false);
      config.lights.enable = memoLightEnable;
      config.effects.fog = memoFog;

      return elem;
    }
  };

  Scene.id = $.time();

  Scene.MAX_TEXTURES = 3;
  Scene.MAX_POINT_LIGHTS = 3;

  PhiloGL.Scene = Scene;

})();
<|endoftext|>


<commit_before>var _ = codebox.require("hr.utils");
var Class = codebox.require("hr.class");
var hash = codebox.require("utils/hash");
var Socket = codebox.require("core/socket");

var logging = require("hr.logger")("terminal");

var Shell = Class.extend({
    defaults: {},

    initialize: function() {
        Shell.__super__.initialize.apply(this, arguments);
        this.shellId = this.options.shellId || _.uniqueId("term");

        return this;
    },

    /*
     *  Connect to the terminal
     */
    connect: function() {
        var that = this;
        if (this.socket != null) {
            return this;
        }

        this.socket = new Socket({
            service: "terminal"
        });

        this.listenTo(this.socket, "close", function() {
            this.trigger("disconnect");
        });

        this.listenTo(this.socket, "do:output", function(data) {
            this.trigger("data", hash.atob(data));
        });

        this.listenTo(this.socket, "open", function() {
            this.trigger("connect");

            this.socket.do('open', {
                "shellId": that.shellId,
                "opts": {
                    "rows": 80,
                    "columns": 24,
                    "id": that.shellId,
                    "cwd": that.options.cwd
                }
            });
        });
    },

    /*
     *  Disconnect
     */
    disconnect: function() {
        if (this.socket != null) {
            this.socket.close();
        }
        return this;
    },

    /*
     *  Write content
     */
    write: function(buf) {
        if (this.socket != null) {
            this.socket.do("input", hash.btoa(buf));
        }
        return this;
    },

    /*
     *  Resize the shell
     */
    resize: function(w, h) {
        if (this.socket != null) {
            this.socket.do("resize", {
                "rows": h,
                "columns": w
            });
        }
        return this;
    },

    /*
     *  Force destroy the shell
     */
    forceDestroy: function() {
        if (this.socket != null) {
            this.socket.do("destroy");
        }
        return this;
    }
});

module.exports = Shell;
<commit_msg>Fix CodeboxIDE/codebox#484: correct hr.logger dependency<commit_after>var _ = codebox.require("hr.utils");
var Class = codebox.require("hr.class");
var hash = codebox.require("utils/hash");
var Socket = codebox.require("core/socket");

var logging = codebox.require("hr.logger")("terminal");

var Shell = Class.extend({
    defaults: {},

    initialize: function() {
        Shell.__super__.initialize.apply(this, arguments);
        this.shellId = this.options.shellId || _.uniqueId("term");

        return this;
    },

    /*
     *  Connect to the terminal
     */
    connect: function() {
        var that = this;
        if (this.socket != null) {
            return this;
        }

        this.socket = new Socket({
            service: "terminal"
        });

        this.listenTo(this.socket, "close", function() {
            this.trigger("disconnect");
        });

        this.listenTo(this.socket, "do:output", function(data) {
            this.trigger("data", hash.atob(data));
        });

        this.listenTo(this.socket, "open", function() {
            this.trigger("connect");

            this.socket.do('open', {
                "shellId": that.shellId,
                "opts": {
                    "rows": 80,
                    "columns": 24,
                    "id": that.shellId,
                    "cwd": that.options.cwd
                }
            });
        });
    },

    /*
     *  Disconnect
     */
    disconnect: function() {
        if (this.socket != null) {
            this.socket.close();
        }
        return this;
    },

    /*
     *  Write content
     */
    write: function(buf) {
        if (this.socket != null) {
            this.socket.do("input", hash.btoa(buf));
        }
        return this;
    },

    /*
     *  Resize the shell
     */
    resize: function(w, h) {
        if (this.socket != null) {
            this.socket.do("resize", {
                "rows": h,
                "columns": w
            });
        }
        return this;
    },

    /*
     *  Force destroy the shell
     */
    forceDestroy: function() {
        if (this.socket != null) {
            this.socket.do("destroy");
        }
        return this;
    }
});

module.exports = Shell;
<|endoftext|>


<commit_before>var Client = require( "slack-client" );

function onConnected() {
	console.log( "Connected to slack" );
}

function onError( err ) {
	if ( err == "invalid_auth" ) {
		console.error( "Slack login Invalid. No messages will be sent." );
	} else {
		console.error( "Slack error: ", err );
	}
}

function onMessage( message ) {
	//console.log( "Got message: ", message.text );
}

function tell( slack, target, message ) {
	if ( !slack.authenticated ) { //escape if bad auth
		return;
	}
	var dm = slack.getDMByName( target );
	if ( !dm ) {
		var user = slack.getUserByName( target );
		slack.openDM( user.id, function() {
			var dm = slack.getDMByName( target );
			dm.send( message );
		} );
	} else {
		dm.send( message );
	}
}

function send( slack, name, message ) {
	if ( !slack.authenticated ) { //escape if bad auth
		return;
	}
	var target = slack.getChannelGroupOrDMByName( name );
	if ( !target ) {
		console.error( "Can't tell", name, "anything. Nothing matching that found." );
	} else {
		target.send( message );
	}
}

var nullReturn = {
	send: function( params ) {},
	tell: function( params ) {}
};

module.exports = function( token ) {
	token = 'abc'
	if ( !token ) { //short circut if Slack is un-configured
		console.warn( "Warning! Slack is not configured and no messages will be sent." );
		return nullReturn;
	}
	var slack = new Client( token, false, true );

	slack.on( "open", onConnected );
	slack.on( "message", onMessage );
	slack.on( "error", onError );

	slack.login();

	return {
		send: send.bind( undefined, slack ),
		tell: tell.bind( undefined, slack )
	};
};
<commit_msg>Removed a debug statement<commit_after>var Client = require( "slack-client" );

function onConnected() {
	console.log( "Connected to slack" );
}

function onError( err ) {
	if ( err == "invalid_auth" ) {
		console.error( "Slack login Invalid. No messages will be sent." );
	} else {
		console.error( "Slack error: ", err );
	}
}

function onMessage( message ) {
	//console.log( "Got message: ", message.text );
}

function tell( slack, target, message ) {
	if ( !slack.authenticated ) { //escape if bad auth
		return;
	}
	var dm = slack.getDMByName( target );
	if ( !dm ) {
		var user = slack.getUserByName( target );
		slack.openDM( user.id, function() {
			var dm = slack.getDMByName( target );
			dm.send( message );
		} );
	} else {
		dm.send( message );
	}
}

function send( slack, name, message ) {
	if ( !slack.authenticated ) { //escape if bad auth
		return;
	}
	var target = slack.getChannelGroupOrDMByName( name );
	if ( !target ) {
		console.error( "Can't tell", name, "anything. Nothing matching that found." );
	} else {
		target.send( message );
	}
}

var nullReturn = {
	send: function( params ) {},
	tell: function( params ) {}
};

module.exports = function( token ) {
	if ( !token ) { //short circut if Slack is un-configured
		console.warn( "Warning! Slack is not configured and no messages will be sent." );
		return nullReturn;
	}
	var slack = new Client( token, false, true );

	slack.on( "open", onConnected );
	slack.on( "message", onMessage );
	slack.on( "error", onError );

	slack.login();

	return {
		send: send.bind( undefined, slack ),
		tell: tell.bind( undefined, slack )
	};
};
<|endoftext|>


<commit_before>import c from './config'
import _knex from 'knex'
import _request from 'request-promise'
import {run, createChannel} from 'yacol'
import {login, getInfo, addToCart} from './alza'
import WS from 'ws'

const API = 'https://slack.com/api/'

const request = _request.defaults({})
const knex = _knex(c.knex)

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'EUR',
  minimumFractionDigits: 2,
});

let state = {}

function* apiCall(name, data={}) {
  for (let k in data) {
    if (typeof data[k] === 'object') data[k] = JSON.stringify(data[k])
  }
  return JSON.parse(yield request.post(`${API}${name}`, {form: {...data, token: state.token}}))
}

export function* connect(token) {
  state = {
    token,
    streams: {},
    pendingActions: {},
    actionsCount: 0,
  }

  const response = yield run(apiCall, 'rtm.connect')

  state = {
    ...state,
    team: response.team,
    bot: response.self,
  }

  const connection = new WS(response.url)
  const stream = createChannel()
  connection.on('message', (data) => stream.put(JSON.parse(data)))
  return stream
}

function nextUUID() {
  return (`${Date.now()}#${state.actionsCount++}`)
}

function amIMentioned(event) {
  if (event.user === state.bot.id) return false
  if (event.channel[0] === 'D') return true
  if (event.text.match(`<@${state.bot.id}>`)) return true
  return false
}

function streamForUser(userId) {
  if (state.streams[userId] == null) {
    state.streams[userId] = createChannel()
    run(listenUser, state.streams[userId], userId)
  }
  return state.streams[userId]
}

export function* listen(stream) {
  while (true) {
    const event = yield stream.take()
    if (event.type === 'message' && event.subtype == null && amIMentioned(event)) {
      streamForUser(event.user).put(event)
    }
    if (event.type === 'action') {
      if (event.callback_id.startsWith('O')) {
        yield run(handleOrderAction, event)
            .catch((e) => run(showError, event.channel.id, null, 'Something went wrong.'))
        continue
      }

      const actionStream = state.pendingActions[event.callback_id]
      if (actionStream) actionStream.put(event)
      else yield run(showError, event.channel.id, event.original_message.ts,
          'The order has timed out. Create a new order please.')
    }
  }
}

function* showError(channel, ts, msg) {
  yield run(apiCall, ts ? 'chat.update' : 'chat.postMessage', {
      channel, ts, as_user: true, text: `:exclamation: ${msg}`, attachments: []
  })
}

function* listenUser(stream, user) {
  const newOrder = () => ({
    id: null,
    items: new Map(),
    totalPrice: 0,
    orderConfirmation: null,
  })

  function setId(order, newId) {
    if (order.id) delete state.pendingActions[order.id]
    if (newId) state.pendingActions[newId] = stream
    return {...order, id: newId}
  }

  function destroyOrder(order) {
    setId(order, null)
  }

  while (true) {
    let order = newOrder()

    while (true) {
      const event = yield stream.take()

      if (event.type === 'action') {
        yield run(finishOrder, stream, order, event.actions[0].name, user)
        break
      }

      if (event.type === 'message') {
        order = setId(order, nextUUID())
        order = yield run(updateOrder, order, event, user)
      }
    }

    destroyOrder(order)
  }
}

function* handleOrderAction(event) {
  const orderId = event.actions[0].value
  const items = yield knex.select('shopId', 'count').from('orderItem').where('order', orderId)

  const msg = event.original_message
  const attachment = msg.attachments[0]
  const action = attachment.actions[0]

  yield run(apiCall, 'chat.update', {channel: event.channel.id, ts: msg.ts, attachments: [{
    ...attachment,
    actions: [{...action, text: `Add to Cart Again`, style: 'default'}]
  }]})

  yield run(login, c.alza.credentials)
  for (let item of items) yield run(addToCart, item.shopId, item.count)
}

function* finishOrder(stream, order, action, user) {
  const {channel, ts, message: {attachments: [attachment]}} = order.orderConfirmation
  function* updateMessage(attachmentUpdate) {
    yield run(apiCall, 'chat.update', {channel, ts, as_user: true,
        attachments: [{...attachment, ...attachmentUpdate}]
    })
  }

  function* cancelOrder() {
    yield run(updateMessage, {
      pretext: `:no_entry_sign: Order canceled:`,
      color: 'danger',
      actions: [],
    })
  }

  if (action === 'cancel') yield run(cancelOrder)

  if (action === 'personal') {
    const dbId = yield storeOrder({user, ts, isCompany: false}, order.items)
    yield run(notifyOfficeManager, order, dbId, user, false)
    yield run(updateMessage, {
      pretext: `:woman: Personal order finished:`,
      color: 'good',
      actions: [],
    })
  }

  if (action === 'company') {
    yield run(updateMessage, {
      actions: [
        {name: 'cancel', text: 'Cancel Order', type: 'button', value: 'cancel', style: 'danger'},
      ],
    })
    console.log(c.approvalTreshold)
    yield run(apiCall, 'chat.postMessage', {
      channel: user, as_user: true, text:
        order.totalPrice < c.approvalTreshold
          ? `:question: Why do you need these items?`
          : `:question: This order is pretty high, who approved it?\n:question: Why do you need it?`
    })

    const event = yield stream.take()

    if (event.type === 'message') {
      order.reason = event.text
      const dbId = yield storeOrder({user, ts, isCompany: true, reason: order.reason}, order.items)
      yield run(notifyOfficeManager, order, dbId, user, true)
      yield run(updateMessage, {
        fields: [...attachment.fields, {title: 'Reason', value: event.text, short: false}],
        pretext: `:office: Company order finished:`,
        color: 'good',
        actions: [],
      })
      yield run(apiCall, 'chat.postMessage', {
        channel: user, as_user: true, text: `:office: Company order finished :point_up:`
      })
    }

    if (event.type === 'action') {
      yield run(cancelOrder)
      yield run(apiCall, 'chat.postMessage', {
        channel: user, as_user: true, text: `:no_entry_sign: Canceling :point_up:`
      })
    }

  }
}

function* notifyOfficeManager(order, dbId, user, isCompany) {
  console.log(dbId)
  const orderTypeText = isCompany ? `:office: Company` : `:woman: Personal`

  const orderAttachment = {
    ...orderToAttachment(order, `${orderTypeText} order from <@${user}>`),
    callback_id: `O${dbId}`,
    actions: [{name: 'add-to-cart', text: 'Add to Cart', type: 'button', value: dbId, style: 'primary'}],
  }

  yield run(apiCall, 'chat.postMessage', {
    channel: c.officeManager, as_user: true,
    attachments: [orderAttachment]
  })
}

function* updateOrder(order, event, user) {
  if (order.orderConfirmation) {
    const {channel, ts} = order.orderConfirmation
    yield run(apiCall, 'chat.delete', {channel, ts})
  }

  const [info, errors] = yield run(orderInfo, parseOrder(event.text))

  for (let i of info.items) {
    if (order.items.get(i.id)) order.items.get(i.id).count += i.count
    else order.items.set(i.id, i)
  }
  order.totalPrice += info.totalPrice

  const orderAttachment = {
    ...orderToAttachment(order, `Please confirm your order:`),
    ...makeOrderActions(order)
  }

  if (errors.length > 0) {
    yield run(apiCall, 'chat.postMessage', {
      channel: user,
      as_user: true,
      text: `:exclamation: I can't find these items:\n${errors.join('\n')}`,
    })
  }

  const orderConfirmation = yield run(apiCall, 'chat.postMessage', {
      channel: user,
      attachments: [orderAttachment],
      as_user: true,
  })

  return {...order, orderConfirmation}
}

function itemsField(order) {
  const itemLines = [...order.items.values()].map((item) => {
    const itemPrice = formatter.format(item.price).padStart(10)
    return `\`${itemPrice}\` <${item.url}|${item.count} x ${item.name}>`
  })

  return {title: 'Items', value: itemLines.join('\n'), short: false}
}

function makeOrderActions(order) {
  return {
    callback_id: order.id,
    actions: [
      {name: 'personal', text: 'Make Personal Order', type: 'button', value: 'personal'},
      {name: 'company', text: 'Make Company Order', type: 'button', value: 'company'},
      {name: 'cancel', text: 'Cancel Order', type: 'button', value: 'cancel', style: 'danger'},
    ],
  }
}

function orderToAttachment(order, text) {
  return {
    title: `Order summary`,
    pretext: text,
    fields: [
      itemsField(order),
      {title: 'Total value', value: formatter.format(order.totalPrice)},
      order.reason && {title: 'Reason', value: order.reason},
    ],
    mrkdwn_in: ['fields'],
  }
}

function parseOrder(text) {
  const re = /\s*(\d*)\s*<(http[^\s]+)>/g
  let matches
  let goods = []
  while ((matches = re.exec(text)) !== null) {
    if (matches[1] === '') matches[1] = '1'
    goods.push({url: matches[2], count: parseInt(matches[1])})
  }
  return goods
}

function* orderInfo(items) {
  let info = []
  let errors = []
  let totalPrice = 0
  yield run(login, c.alza.credentials)
  for (let item of items) {
    yield run(function*() {
      const itemInfo = yield run(getInfo, item.url)
      info.push({...itemInfo, count: item.count, url: item.url})
      totalPrice += itemInfo.price * item.count
    }).catch((e) => {
      errors.push(item.url)
    })
  }
  return [{items: info, totalPrice}, errors]
}

function storeOrder(order, items) {
  return knex.transaction((trx) => run(function*() {
    const id = (yield trx.insert(order, 'id').into('order'))[0]

    for (let item of items.values()) {
      yield trx.insert({
        order: id,
        shopId: item.id,
        count: item.count,
        url: item.url,
        price: item.price
      }).into('orderItem')
    }
    return id
  }))
}
<commit_msg>Remove console.logs.<commit_after>import c from './config'
import _knex from 'knex'
import _request from 'request-promise'
import {run, createChannel} from 'yacol'
import {login, getInfo, addToCart} from './alza'
import WS from 'ws'

const API = 'https://slack.com/api/'

const request = _request.defaults({})
const knex = _knex(c.knex)

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'EUR',
  minimumFractionDigits: 2,
});

let state = {}

function* apiCall(name, data={}) {
  for (let k in data) {
    if (typeof data[k] === 'object') data[k] = JSON.stringify(data[k])
  }
  return JSON.parse(yield request.post(`${API}${name}`, {form: {...data, token: state.token}}))
}

export function* connect(token) {
  state = {
    token,
    streams: {},
    pendingActions: {},
    actionsCount: 0,
  }

  const response = yield run(apiCall, 'rtm.connect')

  state = {
    ...state,
    team: response.team,
    bot: response.self,
  }

  const connection = new WS(response.url)
  const stream = createChannel()
  connection.on('message', (data) => stream.put(JSON.parse(data)))
  return stream
}

function nextUUID() {
  return (`${Date.now()}#${state.actionsCount++}`)
}

function amIMentioned(event) {
  if (event.user === state.bot.id) return false
  if (event.channel[0] === 'D') return true
  if (event.text.match(`<@${state.bot.id}>`)) return true
  return false
}

function streamForUser(userId) {
  if (state.streams[userId] == null) {
    state.streams[userId] = createChannel()
    run(listenUser, state.streams[userId], userId)
  }
  return state.streams[userId]
}

export function* listen(stream) {
  while (true) {
    const event = yield stream.take()
    if (event.type === 'message' && event.subtype == null && amIMentioned(event)) {
      streamForUser(event.user).put(event)
    }
    if (event.type === 'action') {
      if (event.callback_id.startsWith('O')) {
        yield run(handleOrderAction, event)
            .catch((e) => run(showError, event.channel.id, null, 'Something went wrong.'))
        continue
      }

      const actionStream = state.pendingActions[event.callback_id]
      if (actionStream) actionStream.put(event)
      else yield run(showError, event.channel.id, event.original_message.ts,
          'The order has timed out. Create a new order please.')
    }
  }
}

function* showError(channel, ts, msg) {
  yield run(apiCall, ts ? 'chat.update' : 'chat.postMessage', {
      channel, ts, as_user: true, text: `:exclamation: ${msg}`, attachments: []
  })
}

function* listenUser(stream, user) {
  const newOrder = () => ({
    id: null,
    items: new Map(),
    totalPrice: 0,
    orderConfirmation: null,
  })

  function setId(order, newId) {
    if (order.id) delete state.pendingActions[order.id]
    if (newId) state.pendingActions[newId] = stream
    return {...order, id: newId}
  }

  function destroyOrder(order) {
    setId(order, null)
  }

  while (true) {
    let order = newOrder()

    while (true) {
      const event = yield stream.take()

      if (event.type === 'action') {
        yield run(finishOrder, stream, order, event.actions[0].name, user)
        break
      }

      if (event.type === 'message') {
        order = setId(order, nextUUID())
        order = yield run(updateOrder, order, event, user)
      }
    }

    destroyOrder(order)
  }
}

function* handleOrderAction(event) {
  const orderId = event.actions[0].value
  const items = yield knex.select('shopId', 'count').from('orderItem').where('order', orderId)

  const msg = event.original_message
  const attachment = msg.attachments[0]
  const action = attachment.actions[0]

  yield run(apiCall, 'chat.update', {channel: event.channel.id, ts: msg.ts, attachments: [{
    ...attachment,
    actions: [{...action, text: `Add to Cart Again`, style: 'default'}]
  }]})

  yield run(login, c.alza.credentials)
  for (let item of items) yield run(addToCart, item.shopId, item.count)
}

function* finishOrder(stream, order, action, user) {
  const {channel, ts, message: {attachments: [attachment]}} = order.orderConfirmation
  function* updateMessage(attachmentUpdate) {
    yield run(apiCall, 'chat.update', {channel, ts, as_user: true,
        attachments: [{...attachment, ...attachmentUpdate}]
    })
  }

  function* cancelOrder() {
    yield run(updateMessage, {
      pretext: `:no_entry_sign: Order canceled:`,
      color: 'danger',
      actions: [],
    })
  }

  if (action === 'cancel') yield run(cancelOrder)

  if (action === 'personal') {
    const dbId = yield storeOrder({user, ts, isCompany: false}, order.items)
    yield run(notifyOfficeManager, order, dbId, user, false)
    yield run(updateMessage, {
      pretext: `:woman: Personal order finished:`,
      color: 'good',
      actions: [],
    })
  }

  if (action === 'company') {
    yield run(updateMessage, {
      actions: [
        {name: 'cancel', text: 'Cancel Order', type: 'button', value: 'cancel', style: 'danger'},
      ],
    })
    yield run(apiCall, 'chat.postMessage', {
      channel: user, as_user: true, text:
        order.totalPrice < c.approvalTreshold
          ? `:question: Why do you need these items?`
          : `:question: This order is pretty high, who approved it?\n:question: Why do you need it?`
    })

    const event = yield stream.take()

    if (event.type === 'message') {
      order.reason = event.text
      const dbId = yield storeOrder({user, ts, isCompany: true, reason: order.reason}, order.items)
      yield run(notifyOfficeManager, order, dbId, user, true)
      yield run(updateMessage, {
        fields: [...attachment.fields, {title: 'Reason', value: event.text, short: false}],
        pretext: `:office: Company order finished:`,
        color: 'good',
        actions: [],
      })
      yield run(apiCall, 'chat.postMessage', {
        channel: user, as_user: true, text: `:office: Company order finished :point_up:`
      })
    }

    if (event.type === 'action') {
      yield run(cancelOrder)
      yield run(apiCall, 'chat.postMessage', {
        channel: user, as_user: true, text: `:no_entry_sign: Canceling :point_up:`
      })
    }

  }
}

function* notifyOfficeManager(order, dbId, user, isCompany) {
  const orderTypeText = isCompany ? `:office: Company` : `:woman: Personal`

  const orderAttachment = {
    ...orderToAttachment(order, `${orderTypeText} order from <@${user}>`),
    callback_id: `O${dbId}`,
    actions: [{name: 'add-to-cart', text: 'Add to Cart', type: 'button', value: dbId, style: 'primary'}],
  }

  yield run(apiCall, 'chat.postMessage', {
    channel: c.officeManager, as_user: true,
    attachments: [orderAttachment]
  })
}

function* updateOrder(order, event, user) {
  if (order.orderConfirmation) {
    const {channel, ts} = order.orderConfirmation
    yield run(apiCall, 'chat.delete', {channel, ts})
  }

  const [info, errors] = yield run(orderInfo, parseOrder(event.text))

  for (let i of info.items) {
    if (order.items.get(i.id)) order.items.get(i.id).count += i.count
    else order.items.set(i.id, i)
  }
  order.totalPrice += info.totalPrice

  const orderAttachment = {
    ...orderToAttachment(order, `Please confirm your order:`),
    ...makeOrderActions(order)
  }

  if (errors.length > 0) {
    yield run(apiCall, 'chat.postMessage', {
      channel: user,
      as_user: true,
      text: `:exclamation: I can't find these items:\n${errors.join('\n')}`,
    })
  }

  const orderConfirmation = yield run(apiCall, 'chat.postMessage', {
      channel: user,
      attachments: [orderAttachment],
      as_user: true,
  })

  return {...order, orderConfirmation}
}

function itemsField(order) {
  const itemLines = [...order.items.values()].map((item) => {
    const itemPrice = formatter.format(item.price).padStart(10)
    return `\`${itemPrice}\` <${item.url}|${item.count} x ${item.name}>`
  })

  return {title: 'Items', value: itemLines.join('\n'), short: false}
}

function makeOrderActions(order) {
  return {
    callback_id: order.id,
    actions: [
      {name: 'personal', text: 'Make Personal Order', type: 'button', value: 'personal'},
      {name: 'company', text: 'Make Company Order', type: 'button', value: 'company'},
      {name: 'cancel', text: 'Cancel Order', type: 'button', value: 'cancel', style: 'danger'},
    ],
  }
}

function orderToAttachment(order, text) {
  return {
    title: `Order summary`,
    pretext: text,
    fields: [
      itemsField(order),
      {title: 'Total value', value: formatter.format(order.totalPrice)},
      order.reason && {title: 'Reason', value: order.reason},
    ],
    mrkdwn_in: ['fields'],
  }
}

function parseOrder(text) {
  const re = /\s*(\d*)\s*<(http[^\s]+)>/g
  let matches
  let goods = []
  while ((matches = re.exec(text)) !== null) {
    if (matches[1] === '') matches[1] = '1'
    goods.push({url: matches[2], count: parseInt(matches[1])})
  }
  return goods
}

function* orderInfo(items) {
  let info = []
  let errors = []
  let totalPrice = 0
  yield run(login, c.alza.credentials)
  for (let item of items) {
    yield run(function*() {
      const itemInfo = yield run(getInfo, item.url)
      info.push({...itemInfo, count: item.count, url: item.url})
      totalPrice += itemInfo.price * item.count
    }).catch((e) => {
      errors.push(item.url)
    })
  }
  return [{items: info, totalPrice}, errors]
}

function storeOrder(order, items) {
  return knex.transaction((trx) => run(function*() {
    const id = (yield trx.insert(order, 'id').into('order'))[0]

    for (let item of items.values()) {
      yield trx.insert({
        order: id,
        shopId: item.id,
        count: item.count,
        url: item.url,
        price: item.price
      }).into('orderItem')
    }
    return id
  }))
}
<|endoftext|>