<commit_before>Object.assign(pc, function () {

    var _deviceDeprecationWarning = false;
    var _getSelectionDeprecationWarning = false;
    var _prepareDeprecationWarning = false;

    /**
     * @constructor
     * @name pc.Picker
     * @classdesc Picker object used to select mesh instances from screen coordinates.
     * @description Create a new instance of a Picker object
     * @param {pc.Application} app The application managing this picker instance.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     * @property {Number} width Width of the pick buffer in pixels (read-only).
     * @property {Number} height Height of the pick buffer in pixels (read-only).
     * @property {pc.RenderTarget} renderTarget The render target used by the picker internally (read-only).
     */
    var Picker = function (app, width, height) {
        if (app instanceof pc.GraphicsDevice) {
            app = pc.Application.getApplication();
            if (!_deviceDeprecationWarning) {
                _deviceDeprecationWarning = true;
                // #ifdef DEBUG
                console.warn("pc.Picker now takes pc.Application as first argument. Passing pc.GraphicsDevice is deprecated.");
                // #endif
            }
        }

        this.app = app;
        this.device = app.graphicsDevice;
        var device = this.device;

        this.library = device.getProgramLibrary();

        this.pickColor = new Float32Array(4);
        this.pickColor[3] = 1;

        this.scene = null;
        this.drawCalls = [];
        this.layer = null;
        this.layerComp = null;

        this.clearOptions = {
            color: [1, 1, 1, 1],
            depth: 1,
            flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH
        };

        var self = this;
        this._clearDepthOptions = {
            depth: 1.0,
            flags: pc.CLEARFLAG_DEPTH
        };
        this.clearDepthCommand = new pc.Command(0, 0, function (){
            device.clear(self._clearDepthOptions);
        });

        this.resize(width, height);

        this._ignoreOpacityFor = null; // meshInstance
    };

    /**
     * @function
     * @name pc.Picker#getSelection
     * @description Return the list of mesh instances selected by the specified rectangle in the
     * previously prepared pick buffer.The rectangle using top-left coordinate system.
     * @param {Number} x The left edge of the rectangle
     * @param {Number} y The top edge of the rectangle
     * @param {Number} [width] The width of the rectangle
     * @param {Number} [height] The height of the rectangle
     * @returns {pc.MeshInstance[]} An array of mesh instances that are in the selection
     * @example
     * // Get the selection at the point (10,20)
     * var selection = picker.getSelection(10, 20);
     *
     * // Get all models in rectangle with corners at (10,20) and (20,40)
     * var selection = picker.getSelection(10, 20, 10, 20);
     */
    Picker.prototype.getSelection = function (x, y, width, height) {
        var device = this.device;

        if (typeof x === 'object') {
            // #ifdef DEBUG
            if (!_prepareDeprecationWarning) {
                _prepareDeprecationWarning = true;
                console.warn("Picker.getSelection:param 'rect' is deprecated, use 'x, y, width, height' instead.");
            }
            // #endif

            var rect = x;
            x = rect.x;
            y = rect.y;
            width = rect.width;
            height = rect.height;
        } else {
            y = this.layer.renderTarget.height - (y + (height || 1));
        }

        width = width || 1;
        height = height || 1;

        // Cache active render target
        var prevRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this.layer.renderTarget);
        device.updateBegin();

        var pixels = new Uint8Array(4 * width * height);
        device.readPixels(x, y, width, height, pixels);

        device.updateEnd();

        // Restore render target
        device.setRenderTarget(prevRenderTarget);

        var selection = [];

        var drawCalls = this.layer.instances.visibleOpaque[0].list;

        var r, g, b, index;
        for (var i = 0; i < width * height; i++) {
            r = pixels[4 * i + 0];
            g = pixels[4 * i + 1];
            b = pixels[4 * i + 2];
            index = r << 16 | g << 8 | b;
            // White is 'no selection'
            if (index !== 0xffffff) {
                var selectedMeshInstance = drawCalls[index];
                if (selection.indexOf(selectedMeshInstance) === -1) {
                    selection.push(selectedMeshInstance);
                }
            }
        }

        return selection;
    };

    /**
     * @function
     * @name pc.Picker#prepare
     * @description Primes the pick buffer with a rendering of the specified models from the point of view
     * of the supplied camera. Once the pick buffer has been prepared, pc.Picker#getSelection can be
     * called multiple times on the same picker object. Therefore, if the models or camera do not change
     * in any way, pc.Picker#prepare does not need to be called again.
     * @param {pc.CameraComponent} camera The camera component used to render the scene.
     * @param {pc.Scene} scene The scene containing the pickable mesh instances.
     * @param {pc.Layer|pc.RenderTarget} [arg] Layer or RenderTarget from which objects will be picked. If not supplied, all layers rendering to backbuffer before this layer will be used.
     */
    Picker.prototype.prepare = function (camera, scene, arg) {
        var device = this.device;
        var i, j;
        var self = this;

        if (camera instanceof pc.Camera) {
            // #ifdef DEBUG
            if (!_getSelectionDeprecationWarning) {
                _getSelectionDeprecationWarning = true;
                console.warn("pc.Picker#prepare now takes pc.CameraComponent as first argument. Passing pc.Camera is deprecated.");
            }

            // #endif
            camera = camera._component;
        }

        this.scene = scene;
        var sourceLayer = null;
        var sourceRt = null;

        if (arg instanceof pc.Layer) {
            sourceLayer = arg;
        } else {
            sourceRt = arg;
        }

        // Setup picker rendering once
        if (!this.layer) {
            var pickColorId = device.scope.resolve('uColor');

            this.layer = new pc.Layer({
                name: "Picker",
                shaderPass: pc.SHADER_PICK,
                opaqueSortMode: pc.SORTMODE_NONE,

                onEnable: function () {
                    if (this.renderTarget) return;
                    var colorBuffer = new pc.Texture(device, {
                        format: pc.PIXELFORMAT_R8_G8_B8_A8,
                        width: self.width,
                        height: self.height
                    });
                    colorBuffer.minFilter = pc.FILTER_NEAREST;
                    colorBuffer.magFilter = pc.FILTER_NEAREST;
                    colorBuffer.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
                    colorBuffer.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
                    this.renderTarget = new pc.RenderTarget(device, colorBuffer, {
                        depth: true
                    });
                },

                onDisable: function () {
                    if (!this.renderTarget) return;
                    this.renderTarget._colorBuffer.destroy();
                    this.renderTarget.destroy();
                    this.renderTarget = null;
                },

                onDrawCall: function (meshInstance, index) {
                    self.pickColor[0] = ((index >> 16) & 0xff) / 255;
                    self.pickColor[1] = ((index >> 8) & 0xff) / 255;
                    self.pickColor[2] = (index & 0xff) / 255;
                    pickColorId.setValue(self.pickColor);
                    device.setBlending(false);
                },

                // could probably move updateCameraFrustum into onLayerPreRender function
                // and remove everything else
                onPreCull: function () {
                    this.oldAspectMode = this.cameras[0].aspectRatioMode;
                    this.oldAspect = this.cameras[0].aspectRatio;
                    this.cameras[0].aspectRatioMode = pc.ASPECT_MANUAL;
                    var rt = sourceRt ? sourceRt : (sourceLayer ? sourceLayer.renderTarget : null);
                    this.cameras[0].aspectRatio = this.cameras[0].calculateAspectRatio(rt);
                    self.app.renderer.updateCameraFrustum(this.cameras[0].camera);
                },

                // could probably remove this because we've moved
                // prerender/postrender to be outside of renderComposition
                onPostCull: function () {
                    this.cameras[0].aspectRatioMode = this.oldAspectMode;
                    this.cameras[0].aspectRatio = this.oldAspect;
                }
            });

            this.layerComp = new pc.LayerComposition();
            this.layerComp.pushOpaque(this.layer);

            this.meshInstances = this.layer.opaqueMeshInstances;
            this._instancesVersion = -1;
        }

        // Collect pickable mesh instances
        var instanceList, instanceListLength, drawCall;
        if (!sourceLayer) {
            this.layer.clearMeshInstances();
            var layers = scene.layers.layerList;
            var subLayerEnabled = scene.layers.subLayerEnabled;
            var isTransparent = scene.layers.subLayerList;
            var layer;
            var layerCamId, transparent;
            for (i = 0; i < layers.length; i++) {
                if (layers[i].overrideClear && layers[i]._clearDepthBuffer) layers[i]._pickerCleared = false;
            }
            for (i = 0; i < layers.length; i++) {
                layer = layers[i];
                if (layer.renderTarget !== sourceRt || !layer.enabled || !subLayerEnabled[i]) continue;
                layerCamId = layer.cameras.indexOf(camera);
                if (layerCamId < 0) continue;
                if (layer.overrideClear && layer._clearDepthBuffer && !layer._pickerCleared) {
                    this.meshInstances.push(this.clearDepthCommand);
                    layer._pickerCleared = true;
                }
                transparent = isTransparent[i];
                instanceList = transparent ? layer.instances.transparentMeshInstances : layer.instances.opaqueMeshInstances;
                instanceListLength = instanceList.length;
                for (j = 0; j < instanceListLength; j++) {
                    drawCall = instanceList[j];
                    if (drawCall.pick) {
                        this.meshInstances.push(drawCall);
                    }
                }
            }
        } else {
            if (this._instancesVersion !== sourceLayer._version) {
                this.layer.clearMeshInstances();
                instanceList = sourceLayer.instances.opaqueMeshInstances;
                instanceListLength = instanceList.length;
                for (j = 0; j < instanceListLength; j++) {
                    drawCall = instanceList[j];
                    if (drawCall.pick) {
                        this.meshInstances.push(drawCall);
                    }
                }
                instanceList = sourceLayer.instances.transparentMeshInstances;
                instanceListLength = instanceList.length;
                for (j = 0; j < instanceListLength; j++) {
                    drawCall = instanceList[j];
                    if (drawCall.pick) {
                        this.meshInstances.push(drawCall);
                    }
                }
                this._instancesVersion = sourceLayer._version;
            }
        }

        // Setup picker camera if changed
        if (this.layer.cameras[0] !== camera) {
            this.layer.clearCameras();
            this.layer.addCamera(camera);
        }

        // save old camera state
        this.onLayerPreRender(this.layer, sourceLayer, sourceRt);

        // Render
        this.app.renderer.renderComposition(this.layerComp);

        // restore old camera state
        this.onLayerPostRender(this.layer);
    };

    Picker.prototype.onLayerPreRender = function (layer, sourceLayer, sourceRt) {
        if (this.width !== layer.renderTarget.width || this.height !== layer.renderTarget.height) {
            layer.onDisable();
            layer.onEnable();
        }
        layer.oldClear = layer.cameras[0].camera._clearOptions;
        layer.oldAspectMode = layer.cameras[0].aspectRatioMode;
        layer.oldAspect = layer.cameras[0].aspectRatio;
        layer.cameras[0].camera._clearOptions = this.clearOptions;
        layer.cameras[0].aspectRatioMode = pc.ASPECT_MANUAL;
        var rt = sourceRt ? sourceRt : (sourceLayer ? sourceLayer.renderTarget : null);
        layer.cameras[0].aspectRatio = layer.cameras[0].calculateAspectRatio(rt);
    };

    Picker.prototype.onLayerPostRender = function (layer) {
        layer.cameras[0].camera._clearOptions = layer.oldClear;
        layer.cameras[0].aspectRatioMode = layer.oldAspectMode;
        layer.cameras[0].aspectRatio = layer.oldAspect;
    };

    /**
     * @function
     * @name pc.Picker#resize
     * @description Sets the resolution of the pick buffer. The pick buffer resolution does not need
     * to match the resolution of the corresponding frame buffer use for general rendering of the
     * 3D scene. However, the lower the resolution of the pick buffer, the less accurate the selection
     * results returned by pc.Picker#getSelection. On the other hand, smaller pick buffers will
     * yield greater performance, so there is a trade off.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     */
    Picker.prototype.resize = function (width, height) {
        this.width = width;
        this.height = height;
    };

    Object.defineProperty(Picker.prototype, 'renderTarget', {
        get: function () {
            return this.layer.renderTarget;
        }
    });

    return {
        Picker: Picker
    };
}());
<commit_msg>[FIX] Viewport resize when using the picker<commit_after>Object.assign(pc, function () {

    var _deviceDeprecationWarning = false;
    var _getSelectionDeprecationWarning = false;
    var _prepareDeprecationWarning = false;

    /**
     * @constructor
     * @name pc.Picker
     * @classdesc Picker object used to select mesh instances from screen coordinates.
     * @description Create a new instance of a Picker object
     * @param {pc.Application} app The application managing this picker instance.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     * @property {Number} width Width of the pick buffer in pixels (read-only).
     * @property {Number} height Height of the pick buffer in pixels (read-only).
     * @property {pc.RenderTarget} renderTarget The render target used by the picker internally (read-only).
     */
    var Picker = function (app, width, height) {
        if (app instanceof pc.GraphicsDevice) {
            app = pc.Application.getApplication();
            if (!_deviceDeprecationWarning) {
                _deviceDeprecationWarning = true;
                // #ifdef DEBUG
                console.warn("pc.Picker now takes pc.Application as first argument. Passing pc.GraphicsDevice is deprecated.");
                // #endif
            }
        }

        this.app = app;
        this.device = app.graphicsDevice;
        var device = this.device;

        this.library = device.getProgramLibrary();

        this.pickColor = new Float32Array(4);
        this.pickColor[3] = 1;

        this.scene = null;
        this.drawCalls = [];
        this.layer = null;
        this.layerComp = null;

        this.clearOptions = {
            color: [1, 1, 1, 1],
            depth: 1,
            flags: pc.CLEARFLAG_COLOR | pc.CLEARFLAG_DEPTH
        };

        var self = this;
        this._clearDepthOptions = {
            depth: 1.0,
            flags: pc.CLEARFLAG_DEPTH
        };
        this.clearDepthCommand = new pc.Command(0, 0, function (){
            device.clear(self._clearDepthOptions);
        });

        this.resize(width, height);

        this._ignoreOpacityFor = null; // meshInstance
    };

    /**
     * @function
     * @name pc.Picker#getSelection
     * @description Return the list of mesh instances selected by the specified rectangle in the
     * previously prepared pick buffer.The rectangle using top-left coordinate system.
     * @param {Number} x The left edge of the rectangle
     * @param {Number} y The top edge of the rectangle
     * @param {Number} [width] The width of the rectangle
     * @param {Number} [height] The height of the rectangle
     * @returns {pc.MeshInstance[]} An array of mesh instances that are in the selection
     * @example
     * // Get the selection at the point (10,20)
     * var selection = picker.getSelection(10, 20);
     *
     * // Get all models in rectangle with corners at (10,20) and (20,40)
     * var selection = picker.getSelection(10, 20, 10, 20);
     */
    Picker.prototype.getSelection = function (x, y, width, height) {
        var device = this.device;

        if (typeof x === 'object') {
            // #ifdef DEBUG
            if (!_prepareDeprecationWarning) {
                _prepareDeprecationWarning = true;
                console.warn("Picker.getSelection:param 'rect' is deprecated, use 'x, y, width, height' instead.");
            }
            // #endif

            var rect = x;
            x = rect.x;
            y = rect.y;
            width = rect.width;
            height = rect.height;
        } else {
            y = this.layer.renderTarget.height - (y + (height || 1));
        }

        width = width || 1;
        height = height || 1;

        // Cache active render target
        var prevRenderTarget = device.renderTarget;

        // Ready the device for rendering to the pick buffer
        device.setRenderTarget(this.layer.renderTarget);
        device.updateBegin();

        var pixels = new Uint8Array(4 * width * height);
        device.readPixels(x, y, width, height, pixels);

        device.updateEnd();

        // Restore render target
        device.setRenderTarget(prevRenderTarget);

        var selection = [];

        var drawCalls = this.layer.instances.visibleOpaque[0].list;

        var r, g, b, index;
        for (var i = 0; i < width * height; i++) {
            r = pixels[4 * i + 0];
            g = pixels[4 * i + 1];
            b = pixels[4 * i + 2];
            index = r << 16 | g << 8 | b;
            // White is 'no selection'
            if (index !== 0xffffff) {
                var selectedMeshInstance = drawCalls[index];
                if (selection.indexOf(selectedMeshInstance) === -1) {
                    selection.push(selectedMeshInstance);
                }
            }
        }

        return selection;
    };

    /**
     * @function
     * @name pc.Picker#prepare
     * @description Primes the pick buffer with a rendering of the specified models from the point of view
     * of the supplied camera. Once the pick buffer has been prepared, pc.Picker#getSelection can be
     * called multiple times on the same picker object. Therefore, if the models or camera do not change
     * in any way, pc.Picker#prepare does not need to be called again.
     * @param {pc.CameraComponent} camera The camera component used to render the scene.
     * @param {pc.Scene} scene The scene containing the pickable mesh instances.
     * @param {pc.Layer|pc.RenderTarget} [arg] Layer or RenderTarget from which objects will be picked. If not supplied, all layers rendering to backbuffer before this layer will be used.
     */
    Picker.prototype.prepare = function (camera, scene, arg) {
        var device = this.device;
        var i, j;
        var self = this;

        if (camera instanceof pc.Camera) {
            // #ifdef DEBUG
            if (!_getSelectionDeprecationWarning) {
                _getSelectionDeprecationWarning = true;
                console.warn("pc.Picker#prepare now takes pc.CameraComponent as first argument. Passing pc.Camera is deprecated.");
            }

            // #endif
            camera = camera._component;
        }

        this.scene = scene;
        var sourceLayer = null;
        var sourceRt = null;

        if (arg instanceof pc.Layer) {
            sourceLayer = arg;
        } else {
            sourceRt = arg;
        }

        // Setup picker rendering once
        if (!this.layer) {
            var pickColorId = device.scope.resolve('uColor');

            this.layer = new pc.Layer({
                name: "Picker",
                shaderPass: pc.SHADER_PICK,
                opaqueSortMode: pc.SORTMODE_NONE,

                onEnable: function () {
                    if (this.renderTarget) return;
                    var colorBuffer = new pc.Texture(device, {
                        format: pc.PIXELFORMAT_R8_G8_B8_A8,
                        width: self.width,
                        height: self.height
                    });
                    colorBuffer.minFilter = pc.FILTER_NEAREST;
                    colorBuffer.magFilter = pc.FILTER_NEAREST;
                    colorBuffer.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
                    colorBuffer.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
                    this.renderTarget = new pc.RenderTarget(device, colorBuffer, {
                        depth: true
                    });
                },

                onDisable: function () {
                    if (!this.renderTarget) return;
                    this.renderTarget._colorBuffer.destroy();
                    this.renderTarget.destroy();
                    this.renderTarget = null;
                },

                onDrawCall: function (meshInstance, index) {
                    self.pickColor[0] = ((index >> 16) & 0xff) / 255;
                    self.pickColor[1] = ((index >> 8) & 0xff) / 255;
                    self.pickColor[2] = (index & 0xff) / 255;
                    pickColorId.setValue(self.pickColor);
                    device.setBlending(false);
                }
            });

            this.layerComp = new pc.LayerComposition();
            this.layerComp.pushOpaque(this.layer);

            this.meshInstances = this.layer.opaqueMeshInstances;
            this._instancesVersion = -1;
        }

        // Collect pickable mesh instances
        var instanceList, instanceListLength, drawCall;
        if (!sourceLayer) {
            this.layer.clearMeshInstances();
            var layers = scene.layers.layerList;
            var subLayerEnabled = scene.layers.subLayerEnabled;
            var isTransparent = scene.layers.subLayerList;
            var layer;
            var layerCamId, transparent;
            for (i = 0; i < layers.length; i++) {
                if (layers[i].overrideClear && layers[i]._clearDepthBuffer) layers[i]._pickerCleared = false;
            }
            for (i = 0; i < layers.length; i++) {
                layer = layers[i];
                if (layer.renderTarget !== sourceRt || !layer.enabled || !subLayerEnabled[i]) continue;
                layerCamId = layer.cameras.indexOf(camera);
                if (layerCamId < 0) continue;
                if (layer.overrideClear && layer._clearDepthBuffer && !layer._pickerCleared) {
                    this.meshInstances.push(this.clearDepthCommand);
                    layer._pickerCleared = true;
                }
                transparent = isTransparent[i];
                instanceList = transparent ? layer.instances.transparentMeshInstances : layer.instances.opaqueMeshInstances;
                instanceListLength = instanceList.length;
                for (j = 0; j < instanceListLength; j++) {
                    drawCall = instanceList[j];
                    if (drawCall.pick) {
                        this.meshInstances.push(drawCall);
                    }
                }
            }
        } else {
            if (this._instancesVersion !== sourceLayer._version) {
                this.layer.clearMeshInstances();
                instanceList = sourceLayer.instances.opaqueMeshInstances;
                instanceListLength = instanceList.length;
                for (j = 0; j < instanceListLength; j++) {
                    drawCall = instanceList[j];
                    if (drawCall.pick) {
                        this.meshInstances.push(drawCall);
                    }
                }
                instanceList = sourceLayer.instances.transparentMeshInstances;
                instanceListLength = instanceList.length;
                for (j = 0; j < instanceListLength; j++) {
                    drawCall = instanceList[j];
                    if (drawCall.pick) {
                        this.meshInstances.push(drawCall);
                    }
                }
                this._instancesVersion = sourceLayer._version;
            }
        }

        // Setup picker camera if changed
        if (this.layer.cameras[0] !== camera) {
            this.layer.clearCameras();
            this.layer.addCamera(camera);
        }

        // save old camera state
        this.onLayerPreRender(this.layer, sourceLayer, sourceRt);

        // Render
        this.app.renderer.renderComposition(this.layerComp);

        // restore old camera state
        this.onLayerPostRender(this.layer);
    };

    Picker.prototype.onLayerPreRender = function (layer, sourceLayer, sourceRt) {
        if (this.width !== layer.renderTarget.width || this.height !== layer.renderTarget.height) {
            layer.onDisable();
            layer.onEnable();
        }
        layer.oldClear = layer.cameras[0].camera._clearOptions;
        layer.oldAspectMode = layer.cameras[0].aspectRatioMode;
        layer.oldAspect = layer.cameras[0].aspectRatio;
        layer.cameras[0].camera._clearOptions = this.clearOptions;
        layer.cameras[0].aspectRatioMode = pc.ASPECT_MANUAL;
        var rt = sourceRt ? sourceRt : (sourceLayer ? sourceLayer.renderTarget : null);
        layer.cameras[0].aspectRatio = layer.cameras[0].calculateAspectRatio(rt);
        this.app.renderer.updateCameraFrustum(layer.cameras[0].camera);
    };

    Picker.prototype.onLayerPostRender = function (layer) {
        layer.cameras[0].camera._clearOptions = layer.oldClear;
        layer.cameras[0].aspectRatioMode = layer.oldAspectMode;
        layer.cameras[0].aspectRatio = layer.oldAspect;
    };

    /**
     * @function
     * @name pc.Picker#resize
     * @description Sets the resolution of the pick buffer. The pick buffer resolution does not need
     * to match the resolution of the corresponding frame buffer use for general rendering of the
     * 3D scene. However, the lower the resolution of the pick buffer, the less accurate the selection
     * results returned by pc.Picker#getSelection. On the other hand, smaller pick buffers will
     * yield greater performance, so there is a trade off.
     * @param {Number} width The width of the pick buffer in pixels.
     * @param {Number} height The height of the pick buffer in pixels.
     */
    Picker.prototype.resize = function (width, height) {
        this.width = width;
        this.height = height;
    };

    Object.defineProperty(Picker.prototype, 'renderTarget', {
        get: function () {
            return this.layer.renderTarget;
        }
    });

    return {
        Picker: Picker
    };
}());
<|endoftext|>


<commit_before>(function (window) {

    var easingFn = {
        quadratic: {
            transition: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
        },
        circular: {
            transition: 'cubic-bezier(0.1, 0.57, 0.1, 1)'
        },
        back: {
            transition: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
        },
        bounce: {
            transition: 'cubic-bezier(0,0,.5,1.2)'
        }
    };

    // helpers
    var math = {
        distance: function (start, end) {
            return Math.round(end - start);
        },
        velocity: function (distance, duration) {
            return Math.abs(distance) / duration; // px/ms
        },
        direction: function (delta) {
            return delta < 0 ? -1 : delta > 0 ? 1 : 0;
        },
        inertia: function (current, direction, velocity, lower, upper, deceleration) {
            var destination, duration,
                t, a = deceleration || 0.0006;

            t = velocity / a; // a = v / t
            destination = current + (velocity * (t * t) * 0.5 * a * direction);

            if (destination < lower || destination > upper) {
                destination = destination < lower ? lower :
                    destination > upper ? upper :
                    destination;
                t = Math.sqrt(Math.abs(destination - current) / (velocity * 0.5 * a * direction));
            }

            return {
                destination: Math.round(destination),
                duration: parseFloat(t)
            };
        }
    };

    function triggerEvent(type, data) {
        var ev = new CustomEvent(type, {
            detail: data || {},
            bubbles: true,
            cancelable: true
        });

        this.view.dispatchEvent(ev);
    }

    function getCurrentPosition() {
        var style = window.getComputedStyle(this.scroller, null),
            x, y;

        style = (style['transform'] || style['webkitTransform']).split(')')[0].split(', ');
        x = +(style[12] || style[4]);
        y = +(style[13] || style[5]);

        return [x, y];
    }

    function Sv(el, options) {
        var opt = options || {};

        this.view = typeof el === 'string' ? document.querySelector(el) : el;
        this.scroller = this.view.children[0];

        this.options = {
            start: opt.start || [0, 0],

            scrollX: opt.scrollX || false,
            scrollY: opt.scrollY || true,

            inertia: opt.inertia || true,

            bounce: opt.bounce || true,
            bounceTime: opt.bounceTime || 600
        };

        // define initial state
        this._enabled = true;
        this._curPointer = false;

        // event handler
        this._handleStart = this._handleStart.bind(this);
        this._handleMove = this._handleMove.bind(this);
        this._handleEnd = this._handleEnd.bind(this);
        this._handleInertiaEnd = this._handleInertiaEnd.bind(this);

        this.view.addEventListener('pointerdown', this._handleStart, false);

        // inital scroll
        this.scrollTo(this.options.start[0], this.options.start[1]);
    }

    Sv.prototype = {
        version: '0.0.0',

        enable: function (cond) {
            this._enabled = cond === false ? cond : true;
        },

        destroy: function () {

        },

        cancel: function (pointerId) {

        },

        scrollTo: function (x, y, time, easing) {
            var ease = easing || easingFn.circular;

            this.scroller.style['transitionTimingFunction'] =
            this.scroller.style['webkitTransitionTimingFunction'] = ease.transition;
            this.scroller.style['transitionDuration'] =
            this.scroller.style['webkitTransitionDuration'] = (time || 0) + 'ms';

            this._transform(x, y);
        },

        _handleStart: function (ev) {
            if (!this._enabled || this._curPointer) {
                return;
            }

            var timestamp = new Date().getTime();

            this._curPointer = ev.pointerId; // lock for pointer
            this._hasMoved = false;

            this._boundaries = [ // negative numbers because we scroll negative
                -this.view.scrollWidth + this.view.clientWidth,
                -this.view.scrollHeight + this.view.clientHeight
            ];

            // make sure it is not moving anymore
            this._transform.apply(this, getCurrentPosition.call(this));

            // previous point (based on pointer) for delta calculation
            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            // last key frame for velocity caluclation
            this._lastKeyFrame = {
                timestamp: timestamp,
                x: this.x,
                y: this.y
            };

            // bind events
            document.addEventListener('pointerup', this._handleEnd, false);
            document.addEventListener('pointercancel', this._handleEnd, false);
            document.addEventListener('pointermove', this._handleMove, false);
        },

        _handleMove: function (ev) {
            if (!this._enabled || this._curPointer !== ev.pointerId) {
                return;
            }

            var deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                timestamp = new Date().getTime(),
                newX, newY;

            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            newX = this.x + deltaX;
            newY = this.y + deltaY;

            // TODO add WIGGLE_THRESHOLD
            // We need to move at least 10 pixels for the scrolling to initiate
            if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {
                return;
            }

            // consider boundaries
            newX = newX > 0 ? // upper
                this.options.bounce ? this.x + deltaX / 3 :
                0 :
                newX;
            newY = newY > 0 ? // upper
                this.options.bounce ? this.y + deltaY / 3 :
                0 :
                newY;
            newX = newX < this._boundaries[0] ? // lower
                this.options.bounce ? this.x + deltaX / 3 :
                this._boundaries[0] :
                newX;
            newY = newY < this._boundaries[1] ? // lower
                this.options.bounce ? this.y + deltaY / 3 :
                this._boundaries[1] :
                newY;

            // initial move
            if (!this._hasMoved) {
                this._hasMoved = true;
                triggerEvent.call(this, 'scrollstart', {
                    pointerId: this._curPointer,
                    x: this.x,
                    y: this.y
                });
            }

            // save new keyframe every 300ms
            if (timestamp - this._lastKeyFrame.timestamp > 300) {
                this._lastKeyFrame = {
                    timestamp: timestamp,
                    x: newX,
                    y: newY
                }
            }

            this.scrollTo(newX, newY);

            triggerEvent.call(this, 'scroll', {
                pointerId: this._curPointer,
                direction: [
                    math.direction(deltaX),
                    math.direction(deltaY)
                ],
                x: newX,
                y: newY
            });
        },

        _handleEnd: function (ev) {
            if (!this._enabled ||
                this._curPointer !== ev.pointerId) {
                return;
            }

            var duration = new Date().getTime() - this._lastKeyFrame.timestamp,
                deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                distance, velocity, inertia, time,
                newX = this.x + deltaX,
                newY = this.y + deltaY;

            // reset state
            this._curPointer = null;

            document.removeEventListener('pointerup', this._handleEnd, false);
            document.removeEventListener('pointercancel', this._handleEnd, false);
            document.removeEventListener('pointermove', this._handleMove, false);
            this.scrollTo(newX, newY);

            if (!this._hasMoved) { // has never scrolled
                return
            }

            // start momentum animation if needed
            if (this.options.inertia && duration < 300) {
                distance = [
                    math.distance(this._lastKeyFrame.x, this.x),
                    math.distance(this._lastKeyFrame.y, this.y)
                ];

                direction = [
                    math.direction(distance[0]),
                    math.direction(distance[1])
                ];

                velocity = [
                    math.velocity(distance[0], duration),
                    math.velocity(distance[1], duration)
                ];

                // TODO add bounce spacing
                inertia = [ //inertia
                    math.inertia(this.x, direction[0], velocity[0], this._boundaries[0], 0),
                    math.inertia(this.y, direction[1], velocity[1], this._boundaries[1], 0)
                ];

                newX = inertia[0].destination;
                newY = inertia[1].destination;
                time = Math.max(inertia[0].duration, inertia[1].duration);
            }

            if (this.x <= 0 && this.x >= this._boundaries[0] && // not already in bouncing state
                this.y <= 0 && this.y >= this._boundaries[1] &&
                (newX != this.x || newY != this.y)) {
                this.scroller.addEventListener('transitionEnd', this._handleInertiaEnd, false);
                this.scroller.addEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

                this.scrollTo(newX, newY, time);
            } else {
                this._startBounceTransition();
            }

            // TODO wait with eventing for transition
            if (ev.type === 'pointercancel') {
                triggerEvent.call(this, 'scrollcancel', {
                    pointerId: this._curPointer
                });
            }

            triggerEvent.call(this, 'scrollend', {
                pointerId: this._curPointer
            });
        },

        _handleInertiaEnd: function (ev) {
            this.scroller.removeEventListener('transitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

            this._startBounceTransition();
        },

        _startBounceTransition: function () {
            var newX = this.x > 0 ? 0 :
                this.x < this._boundaries[0] ? this._boundaries[0] :
                this.x,
                newY = this.y > 0 ? 0 :
                this.y < this._boundaries[1] ? this._boundaries[1] :
                this.y;

            if (this.options.bounce) {
                // todo attach rAF to surveille computed style
                this.scrollTo(newX, newY, this.options.bounceTime);
            }
        },

        _transform: function (x, y) {
            this.x = Math.round(x);
            this.y = Math.round(y);

            var transform = 'translate(' + this.x + 'px,' + this.y + 'px)',
                translateZ = ' translateZ(0)';

            this.scroller.style['transform'] =
            this.scroller.style['webkitTransform'] = transform + translateZ;
        }

    };

    window.ScrollView = Sv;

}(this));
<commit_msg>move transform and boundaries<commit_after>(function (window) {

    var easingFn = {
        quadratic: {
            transition: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'
        },
        circular: {
            transition: 'cubic-bezier(0.1, 0.57, 0.1, 1)'
        },
        back: {
            transition: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
        },
        bounce: {
            transition: 'cubic-bezier(0,0,.5,1.2)'
        }
    };

    // helpers
    var math = {
        distance: function (start, end) {
            return Math.round(end - start);
        },
        velocity: function (distance, duration) {
            return Math.abs(distance) / duration; // px/ms
        },
        direction: function (delta) {
            return delta < 0 ? -1 : delta > 0 ? 1 : 0;
        },
        inertia: function (current, direction, velocity, lower, upper, deceleration) {
            var destination, duration,
                t, a = deceleration || 0.0006;

            t = velocity / a; // a = v / t
            destination = current + (velocity * (t * t) * 0.5 * a * direction);

            if (destination < lower || destination > upper) {
                destination = destination < lower ? lower :
                    destination > upper ? upper :
                    destination;
                t = Math.sqrt(Math.abs(destination - current) / (velocity * 0.5 * a * direction));
            }

            return {
                destination: Math.round(destination),
                duration: parseFloat(t)
            };
        }
    };

    function triggerEvent(elem, type, data) {
        var ev = new CustomEvent(type, {
            detail: data || {},
            bubbles: true,
            cancelable: true
        });

        elem.dispatchEvent(ev);
    }

    function getCurrentPosition() {
        var style = window.getComputedStyle(this.scroller, null),
            match,
            x, y;
        console.log(style['transform'] || style['webkitTransform']);
        style = (style['transform'] || style['webkitTransform']).match(/matrix\( *([0-9]+)px\ *, *([0-9]+)px *\)/);

        style = (style['transform'] || style['webkitTransform']).split(')')[0].split(', ');
        x = +(style[12] || style[4]);
        y = +(style[13] || style[5]);

        console.log(x, y);
        return [x, y];
    }

    function Sv(el, options) {
        var opt = options || {};

        this.view = typeof el === 'string' ? document.querySelector(el) : el;
        this.scroller = this.view.children[0];

        this.options = {
            start: opt.start || [0, 0],

            scrollX: opt.scrollX || false,
            scrollY: opt.scrollY || true,

            inertia: opt.inertia || true,

            bounce: opt.bounce || true,
            bounceTime: opt.bounceTime || 600
        };

        // define initial state
        this._enabled = true;
        this._curPointer = false;

        // event handler
        this._handleStart = this._handleStart.bind(this);
        this._handleMove = this._handleMove.bind(this);
        this._handleEnd = this._handleEnd.bind(this);
        this._handleInertiaEnd = this._handleInertiaEnd.bind(this);

        this.view.addEventListener('pointerdown', this._handleStart, false);

        // inital scroll
        this.scrollTo(this.options.start[0], this.options.start[1]);
    }

    Sv.prototype = {
        version: '0.0.0',

        enable: function (cond) {
            this._enabled = cond === false ? cond : true;
        },

        destroy: function () {

        },

        cancel: function (pointerId) {

        },

        scrollTo: function (x, y, time, easing) {
            var ease = easing || easingFn.circular;

            this.scroller.style['transitionTimingFunction'] =
            this.scroller.style['webkitTransitionTimingFunction'] = ease.transition;
            this.scroller.style['transitionDuration'] =
            this.scroller.style['webkitTransitionDuration'] = (time || 0) + 'ms';

            this._transform(x, y);
        },

        _handleStart: function (ev) {
            if (!this._enabled || this._curPointer) {
                return;
            }

            var timestamp = new Date().getTime();

            this._curPointer = ev.pointerId; // lock for pointer
            this._hasMoved = false;

            this._getBoundaries();

            // make sure it is not moving anymore
            this._transform.apply(this, this._getTransformPosition() || [0, 0]);

            // previous point (based on pointer) for delta calculation
            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            // last key frame for velocity caluclation
            this._lastKeyFrame = {
                timestamp: timestamp,
                x: this.x,
                y: this.y
            };

            // bind events
            document.addEventListener('pointerup', this._handleEnd, false);
            document.addEventListener('pointercancel', this._handleEnd, false);
            document.addEventListener('pointermove', this._handleMove, false);
        },

        _handleMove: function (ev) {
            if (!this._enabled || this._curPointer !== ev.pointerId) {
                return;
            }

            var deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                timestamp = new Date().getTime(),
                newX, newY;

            this._lastPoint = {
                timestamp: timestamp,
                x: ev.pageX,
                y: ev.pageY
            };

            newX = this.x + deltaX;
            newY = this.y + deltaY;

            // TODO add WIGGLE_THRESHOLD
            // We need to move at least 10 pixels for the scrolling to initiate
            if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {
                return;
            }

            // consider boundaries
            newX = newX > 0 ? // upper
                this.options.bounce ? this.x + deltaX / 3 :
                0 :
                newX;
            newY = newY > 0 ? // upper
                this.options.bounce ? this.y + deltaY / 3 :
                0 :
                newY;
            newX = newX < this._boundaries[0] ? // lower
                this.options.bounce ? this.x + deltaX / 3 :
                this._boundaries[0] :
                newX;
            newY = newY < this._boundaries[1] ? // lower
                this.options.bounce ? this.y + deltaY / 3 :
                this._boundaries[1] :
                newY;

            // initial move
            if (!this._hasMoved) {
                this._hasMoved = true;
                triggerEvent(this.view, 'scrollstart', {
                    pointerId: this._curPointer,
                    x: this.x,
                    y: this.y
                });
            }

            // save new keyframe every 300ms
            if (timestamp - this._lastKeyFrame.timestamp > 300) {
                this._lastKeyFrame = {
                    timestamp: timestamp,
                    x: newX,
                    y: newY
                }
            }

            this.scrollTo(newX, newY);

            triggerEvent(this.view, 'scroll', {
                pointerId: this._curPointer,
                direction: [
                    math.direction(deltaX),
                    math.direction(deltaY)
                ],
                x: newX,
                y: newY
            });
        },

        _handleEnd: function (ev) {
            if (!this._enabled ||
                this._curPointer !== ev.pointerId) {
                return;
            }

            var duration = new Date().getTime() - this._lastKeyFrame.timestamp,
                deltaX = this.options.scrollX ? math.distance(this._lastPoint.x, ev.pageX) : 0,
                deltaY = this.options.scrollY ? math.distance(this._lastPoint.y, ev.pageY) : 0,
                distance, velocity, inertia, time,
                newX = this.x + deltaX,
                newY = this.y + deltaY;

            // reset state
            this._curPointer = null;

            document.removeEventListener('pointerup', this._handleEnd, false);
            document.removeEventListener('pointercancel', this._handleEnd, false);
            document.removeEventListener('pointermove', this._handleMove, false);
            this.scrollTo(newX, newY);

            if (!this._hasMoved) { // has never scrolled
                return
            }

            // start momentum animation if needed
            if (this.options.inertia && duration < 300) {
                distance = [
                    math.distance(this._lastKeyFrame.x, this.x),
                    math.distance(this._lastKeyFrame.y, this.y)
                ];

                direction = [
                    math.direction(distance[0]),
                    math.direction(distance[1])
                ];

                velocity = [
                    math.velocity(distance[0], duration),
                    math.velocity(distance[1], duration)
                ];

                // TODO add bounce spacing
                inertia = [ //inertia
                    math.inertia(this.x, direction[0], velocity[0], this._boundaries[0], 0),
                    math.inertia(this.y, direction[1], velocity[1], this._boundaries[1], 0)
                ];

                newX = inertia[0].destination;
                newY = inertia[1].destination;
                time = Math.max(inertia[0].duration, inertia[1].duration);
            }

            if (this.x <= 0 && this.x >= this._boundaries[0] && // not already in bouncing state
                this.y <= 0 && this.y >= this._boundaries[1] &&
                (newX != this.x || newY != this.y)) {
                this.scroller.addEventListener('transitionEnd', this._handleInertiaEnd, false);
                this.scroller.addEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

                this.scrollTo(newX, newY, time);
            } else {
                this._startBounceTransition();
            }

            // TODO wait with eventing for transition
            if (ev.type === 'pointercancel') {
                triggerEvent(this.view, 'scrollcancel', {
                    pointerId: this._curPointer
                });
            }

            triggerEvent(this.view, 'scrollend', {
                pointerId: this._curPointer
            });
        },

        _handleInertiaEnd: function (ev) {
            this.scroller.removeEventListener('transitionEnd', this._handleInertiaEnd, false);
            this.scroller.removeEventListener('webkitTransitionEnd', this._handleInertiaEnd, false);

            this._startBounceTransition();
        },

        _startBounceTransition: function () {
            var newX = this.x > 0 ? 0 :
                this.x < this._boundaries[0] ? this._boundaries[0] :
                this.x,
                newY = this.y > 0 ? 0 :
                this.y < this._boundaries[1] ? this._boundaries[1] :
                this.y;

            if (this.options.bounce) {
                // todo attach rAF to surveille computed style
                this.scrollTo(newX, newY, this.options.bounceTime);
            }
        },

        _transform: function (x, y) {
            this.x = Math.round(x);
            this.y = Math.round(y);

            var transform = 'translate(' + this.x + 'px,' + this.y + 'px)',
                translateZ = ' translateZ(0)';

            this.scroller.style['transform'] =
            this.scroller.style['webkitTransform'] = transform + translateZ;
        },

        _getTransformPosition: function () {
            var style = window.getComputedStyle(this.scroller, null),
                match;

            match = (style['transform'] || style['webkitTransform'])
                .match(/matrix\( *([0-9]+)px\ *, *([0-9]+)px *\)/);

            if (match) {
                return [x, y];
            }

            return null;
        },

        _getBoundaries: function () {
            return this._boundaries = [ // negative numbers because we scroll negative
                -this.view.scrollWidth + this.view.clientWidth,
                -this.view.scrollHeight + this.view.clientHeight
            ];
        }

    };

    window.ScrollView = Sv;

}(this));
<|endoftext|>


<commit_before>'use strict';

import {generateEventHandlerForElement, applyOptionsToScollBarElement} from './scrollviewhelper'

export class ScrollView {
    constructor(aParentInstance, aOptions) {
        this._parent = aParentInstance._container;
        this._scrollerParent = aParentInstance;
        // setup scroller elements
        this._xElement = document.createElement('div');
        this._yElement = document.createElement('div');

        // create the event handler for the scroller elements
        this._xEventListener = generateEventHandlerForElement.call(this, 'pageX', 'setScrollLeft');
        this._yEventListener = generateEventHandlerForElement.call(this, 'pageY', 'setScrollTop');

        // style some x specific things
        this._xElement.style.height = '6px';
        this._xElement.style.left = '0px';

        // style some y specific things
        this._yElement.style.width = '6px';
        this._yElement.style.top = '0px';

        // style some styles that should apply to x and y
        this._xElement.style.position = this._yElement.style.position = 'absolute';
        this._xElement.style.backgroundColor = this._yElement.style.backgroundColor = 'rgba(0,0,0,0.6)';
        this._xElement.style.borderRadius = this._yElement.style.borderRadius = '3px';

        // and apply the options to the scrollbar elements
        applyOptionsToScollBarElement(this._xElement, 'xElement', aOptions);
        applyOptionsToScollBarElement(this._yElement, 'yElement', aOptions);

        // and append the elements to the DOM tree
        if (!aOptions.disableXScrolling) {
            this._parent.appendChild(this._xElement);
        }
        if (!aOptions.disableYScrolling) {
            this._parent.appendChild(this._yElement);
        }

        // then append the event listeners to x
        Object.keys(this._xEventListener).forEach((aKey) => {
            this._xElement.addEventListener(aKey, this._xEventListener[aKey]);
        });

        // and y
        Object.keys(this._yEventListener).forEach((aKey) => {
            this._yElement.addEventListener(aKey, this._yEventListener[aKey]);
        });

        // and call all update functions initially
        this.parentUpdated();
        this.scrollTopUpdated();
        this.scrollLeftUpdated();
    }

    scrollTopUpdated(aScrollTop) {
        if (this._parentScrollHeight > this._parentHeight) {
            let partSize = aScrollTop / (this._parentScrollHeight - this._parentHeight);
            partSize = partSize * (this._parentHeight - this._elementHeight);
            this._yElement.style.top = (aScrollTop + partSize) + 'px';
        }

        this._xElement.style.top = (aScrollTop + this._parentHeight - 6) + 'px';
    }

    scrollLeftUpdated(aScrollLeft) {
        if (this._parentScrollWidth > this._parentWidth) {
            let partSize = aScrollLeft / (this._parentScrollWidth - this._parentWidth);
            partSize = partSize * (this._parentWidth - this._elementWidth);
            this._xElement.style.left = (aScrollLeft + partSize) + 'px';
        }

        this._yElement.style.left = (aScrollLeft + this._parentWidth - 6) + 'px';
    }

    parentUpdated() {
        this._parentWidth = this._parent.clientWidth;
        this._parentScrollWidth = this._parent.scrollWidth;
        this._elementWidth = this._parentWidth * this._parentWidth / this._parentScrollWidth;
        this._parentHeight = this._parent.clientHeight;
        this._parentScrollHeight = this._parent.scrollHeight;
        this._elementHeight = this._parentHeight * this._parentHeight / this._parentScrollHeight;

        if (this._parentWidth < this._parentScrollWidth) {
            this._xElement.style.display = 'block';
            this._xElement.style.width = this._elementWidth + 'px';

            this.scrollLeftUpdated(this._parent.scrollLeft);
        }
        else {
            this._xElement.style.display = 'none';
        }

        if (this._parentHeight < this._parentScrollHeight) {
            this._yElement.style.display = 'block';
            this._yElement.style.height = this._elementHeight + 'px';

            this.scrollTopUpdated(this._parent.scrollTop);
        }
        else {
            this._yElement.style.display = 'none';
        }
    }

    destroy() {
        Object.keys(this._xEventListener).forEach((aKey) => {
            this._xElement.removeEventListener(aKey, this._xEventListener[aKey]);
        });

        Object.keys(this._yEventListener).forEach((aKey) => {
            this._yElement.removeEventListener(aKey, this._yEventListener[aKey]);
        });

        this._parent = null;
        this._scrollerParent = null;
        this._xElement = null;
        this._yElement = null;
    }
}<commit_msg>Corrected initialization of scrollview<commit_after>'use strict';

import {generateEventHandlerForElement, applyOptionsToScollBarElement} from './scrollviewhelper'

export class ScrollView {
    constructor(aParentInstance, aOptions) {
        this._parent = aParentInstance._container;
        this._scrollerParent = aParentInstance;
        // setup scroller elements
        this._xElement = document.createElement('div');
        this._yElement = document.createElement('div');

        // create the event handler for the scroller elements
        this._xEventListener = generateEventHandlerForElement.call(this, 'pageX', 'setScrollLeft');
        this._yEventListener = generateEventHandlerForElement.call(this, 'pageY', 'setScrollTop');

        // style some x specific things
        this._xElement.style.height = '6px';
        this._xElement.style.left = '0px';

        // style some y specific things
        this._yElement.style.width = '6px';
        this._yElement.style.top = '0px';

        // style some styles that should apply to x and y
        this._xElement.style.position = this._yElement.style.position = 'absolute';
        this._xElement.style.backgroundColor = this._yElement.style.backgroundColor = 'rgba(0,0,0,0.6)';
        this._xElement.style.borderRadius = this._yElement.style.borderRadius = '3px';

        // and apply the options to the scrollbar elements
        applyOptionsToScollBarElement(this._xElement, 'xElement', aOptions);
        applyOptionsToScollBarElement(this._yElement, 'yElement', aOptions);

        // and append the elements to the DOM tree
        if (!aOptions.disableXScrolling) {
            this._parent.appendChild(this._xElement);
        }
        if (!aOptions.disableYScrolling) {
            this._parent.appendChild(this._yElement);
        }

        // then append the event listeners to x
        Object.keys(this._xEventListener).forEach((aKey) => {
            this._xElement.addEventListener(aKey, this._xEventListener[aKey]);
        });

        // and y
        Object.keys(this._yEventListener).forEach((aKey) => {
            this._yElement.addEventListener(aKey, this._yEventListener[aKey]);
        });

        // and call all update functions initially
        this.parentUpdated();
        this.scrollTopUpdated(this._parent.scrollTop);
        this.scrollLeftUpdated(this._parent.scrollLeft);
    }

    scrollTopUpdated(aScrollTop) {
        if (this._parentScrollHeight > this._parentHeight) {
            let partSize = aScrollTop / (this._parentScrollHeight - this._parentHeight);
            partSize = partSize * (this._parentHeight - this._elementHeight);
            this._yElement.style.top = (aScrollTop + partSize) + 'px';
        }

        this._xElement.style.top = (aScrollTop + this._parentHeight - 6) + 'px';
    }

    scrollLeftUpdated(aScrollLeft) {
        if (this._parentScrollWidth > this._parentWidth) {
            let partSize = aScrollLeft / (this._parentScrollWidth - this._parentWidth);
            partSize = partSize * (this._parentWidth - this._elementWidth);
            this._xElement.style.left = (aScrollLeft + partSize) + 'px';
        }

        this._yElement.style.left = (aScrollLeft + this._parentWidth - 6) + 'px';
    }

    parentUpdated() {
        this._parentWidth = this._parent.clientWidth;
        this._parentScrollWidth = this._parent.scrollWidth;
        this._elementWidth = this._parentWidth * this._parentWidth / this._parentScrollWidth;
        this._parentHeight = this._parent.clientHeight;
        this._parentScrollHeight = this._parent.scrollHeight;
        this._elementHeight = this._parentHeight * this._parentHeight / this._parentScrollHeight;

        if (this._parentWidth < this._parentScrollWidth) {
            this._xElement.style.display = 'block';
            this._xElement.style.width = this._elementWidth + 'px';

            this.scrollLeftUpdated(this._parent.scrollLeft);
        }
        else {
            this._xElement.style.display = 'none';
        }

        if (this._parentHeight < this._parentScrollHeight) {
            this._yElement.style.display = 'block';
            this._yElement.style.height = this._elementHeight + 'px';

            this.scrollTopUpdated(this._parent.scrollTop);
        }
        else {
            this._yElement.style.display = 'none';
        }
    }

    destroy() {
        Object.keys(this._xEventListener).forEach((aKey) => {
            this._xElement.removeEventListener(aKey, this._xEventListener[aKey]);
        });

        Object.keys(this._yEventListener).forEach((aKey) => {
            this._yElement.removeEventListener(aKey, this._yEventListener[aKey]);
        });

        this._parent = null;
        this._scrollerParent = null;
        this._xElement = null;
        this._yElement = null;
    }
}<|endoftext|>


<commit_before>// Seeder
// -------

var fs       = require('fs');
var path     = require('path');
var mkdirp   = require('mkdirp');
var Promise  = require('../promise');
import {filter, includes, map, bind, template, each, extend} from 'lodash'

// The new seeds we're performing, typically called from the `knex.seed`
// interface on the main `knex` object. Passes the `knex` instance performing
// the seeds.
function Seeder(knex) {
  this.knex   = knex;
  this.config = this.setConfig(knex.client.config.seeds);
}

// Runs all seed files for the given environment.
Seeder.prototype.run = Promise.method(function(config) {
  this.config = this.setConfig(config);
  return this._seedData()
    .bind(this)
    .spread(function(all) {
      return this._runSeeds(all);
    });
});

// Creates a new seed file, with a given name.
Seeder.prototype.make = function(name, config) {
  this.config = this.setConfig(config);
  if (!name) Promise.rejected(new Error('A name must be specified for the generated seed'));
  return this._ensureFolder(config)
    .bind(this)
    .then(this._generateStubTemplate)
    .then(this._writeNewSeed(name));
};

// Lists all available seed files as a sorted array.
Seeder.prototype._listAll = Promise.method(function(config) {
  this.config = this.setConfig(config);
  return Promise.promisify(fs.readdir, {context: fs})(this._absoluteConfigDir())
    .bind(this)
    .then(function(seeds) {
      return filter(seeds, function(value) {
        var extension = path.extname(value);
        return includes(['.co', '.coffee', '.eg', '.iced', '.js', '.litcoffee', '.ls'], extension);
      }).sort();
    });
});

// Gets the seed file list from the specified seed directory.
Seeder.prototype._seedData = function() {
  return Promise.join(this._listAll());
};

// Ensures a folder for the seeds exist, dependent on the
// seed config settings.
Seeder.prototype._ensureFolder = function() {
    var dir = this._absoluteConfigDir();
    return Promise.promisify(fs.stat, {context: fs})(dir)
      .catch(function() {
        return Promise.promisify(mkdirp)(dir);
      });
};

// Run seed files, in sequence.
Seeder.prototype._runSeeds = function(seeds) {
  return Promise.all(map(seeds, bind(this._validateSeedStructure, this)))
    .bind(this)
    .then(function(seeds) {
      return Promise.bind(this)
        .then(function() {
          return this._waterfallBatch(seeds);
        });
    });
};

// Validates seed files by requiring and checking for a `seed` function.
Seeder.prototype._validateSeedStructure = function(name) {
  var seed = require(path.join(this._absoluteConfigDir(), name));
  if (typeof seed.seed !== 'function') {
    throw new Error('Invalid seed file: ' + name + ' must have a seed function');
  }
  return name;
};

// Generates the stub template for the current seed file, returning a compiled template.
Seeder.prototype._generateStubTemplate = function() {
  var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub');
  return Promise.promisify(fs.readFile, {context: fs})(stubPath).then(function(stub) {
    return template(stub.toString(), null, {variable: 'd'});
  });
};

// Write a new seed to disk, using the config and generated filename,
// passing any `variables` given in the config to the template.
Seeder.prototype._writeNewSeed = function(name) {
  var config = this.config;
  var dir = this._absoluteConfigDir();
  return function(tmpl) {
    if (name[0] === '-') name = name.slice(1);
    var filename  = name + '.' + config.extension;
    return Promise.promisify(fs.writeFile, {context: fs})(
      path.join(dir, filename),
      tmpl(config.variables || {})
    ).return(path.join(dir, filename));
  };
};

// Runs a batch of seed files.
Seeder.prototype._waterfallBatch = function(seeds) {
  var knex      = this.knex;
  var seedDirectory = this._absoluteConfigDir();
  var current   = Promise.bind({failed: false, failedOn: 0});
  var log       = [];
  each(seeds, function(seed) {
    var name  = path.join(seedDirectory, seed);
    seed = require(name);

    // Run each seed file.
    current = current.then(function() {
      return seed.seed(knex, Promise);
    }).then(function() {
      log.push(name);
    });
  });

  return current.thenReturn([log]);
};

Seeder.prototype._absoluteConfigDir = function() {
  return path.resolve(process.cwd(), this.config.directory);
};

Seeder.prototype.setConfig = function(config) {
  return extend({
    extension: 'js',
    directory: './seeds'
  }, this.config || {}, config);
};

module.exports = Seeder;
<commit_msg>Prevent crash on `seed:make` - `template` no longer accepts 3 arguments. Fixed by removing `null`<commit_after>// Seeder
// -------

var fs       = require('fs');
var path     = require('path');
var mkdirp   = require('mkdirp');
var Promise  = require('../promise');
import {filter, includes, map, bind, template, each, extend} from 'lodash'

// The new seeds we're performing, typically called from the `knex.seed`
// interface on the main `knex` object. Passes the `knex` instance performing
// the seeds.
function Seeder(knex) {
  this.knex   = knex;
  this.config = this.setConfig(knex.client.config.seeds);
}

// Runs all seed files for the given environment.
Seeder.prototype.run = Promise.method(function(config) {
  this.config = this.setConfig(config);
  return this._seedData()
    .bind(this)
    .spread(function(all) {
      return this._runSeeds(all);
    });
});

// Creates a new seed file, with a given name.
Seeder.prototype.make = function(name, config) {
  this.config = this.setConfig(config);
  if (!name) Promise.rejected(new Error('A name must be specified for the generated seed'));
  return this._ensureFolder(config)
    .bind(this)
    .then(this._generateStubTemplate)
    .then(this._writeNewSeed(name));
};

// Lists all available seed files as a sorted array.
Seeder.prototype._listAll = Promise.method(function(config) {
  this.config = this.setConfig(config);
  return Promise.promisify(fs.readdir, {context: fs})(this._absoluteConfigDir())
    .bind(this)
    .then(function(seeds) {
      return filter(seeds, function(value) {
        var extension = path.extname(value);
        return includes(['.co', '.coffee', '.eg', '.iced', '.js', '.litcoffee', '.ls'], extension);
      }).sort();
    });
});

// Gets the seed file list from the specified seed directory.
Seeder.prototype._seedData = function() {
  return Promise.join(this._listAll());
};

// Ensures a folder for the seeds exist, dependent on the
// seed config settings.
Seeder.prototype._ensureFolder = function() {
    var dir = this._absoluteConfigDir();
    return Promise.promisify(fs.stat, {context: fs})(dir)
      .catch(function() {
        return Promise.promisify(mkdirp)(dir);
      });
};

// Run seed files, in sequence.
Seeder.prototype._runSeeds = function(seeds) {
  return Promise.all(map(seeds, bind(this._validateSeedStructure, this)))
    .bind(this)
    .then(function(seeds) {
      return Promise.bind(this)
        .then(function() {
          return this._waterfallBatch(seeds);
        });
    });
};

// Validates seed files by requiring and checking for a `seed` function.
Seeder.prototype._validateSeedStructure = function(name) {
  var seed = require(path.join(this._absoluteConfigDir(), name));
  if (typeof seed.seed !== 'function') {
    throw new Error('Invalid seed file: ' + name + ' must have a seed function');
  }
  return name;
};

// Generates the stub template for the current seed file, returning a compiled template.
Seeder.prototype._generateStubTemplate = function() {
  var stubPath = this.config.stub || path.join(__dirname, 'stub', this.config.extension + '.stub');
  return Promise.promisify(fs.readFile, {context: fs})(stubPath).then(function(stub) {
    return template(stub.toString(), {variable: 'd'});
  });
};

// Write a new seed to disk, using the config and generated filename,
// passing any `variables` given in the config to the template.
Seeder.prototype._writeNewSeed = function(name) {
  var config = this.config;
  var dir = this._absoluteConfigDir();
  return function(tmpl) {
    if (name[0] === '-') name = name.slice(1);
    var filename  = name + '.' + config.extension;
    return Promise.promisify(fs.writeFile, {context: fs})(
      path.join(dir, filename),
      tmpl(config.variables || {})
    ).return(path.join(dir, filename));
  };
};

// Runs a batch of seed files.
Seeder.prototype._waterfallBatch = function(seeds) {
  var knex      = this.knex;
  var seedDirectory = this._absoluteConfigDir();
  var current   = Promise.bind({failed: false, failedOn: 0});
  var log       = [];
  each(seeds, function(seed) {
    var name  = path.join(seedDirectory, seed);
    seed = require(name);

    // Run each seed file.
    current = current.then(function() {
      return seed.seed(knex, Promise);
    }).then(function() {
      log.push(name);
    });
  });

  return current.thenReturn([log]);
};

Seeder.prototype._absoluteConfigDir = function() {
  return path.resolve(process.cwd(), this.config.directory);
};

Seeder.prototype.setConfig = function(config) {
  return extend({
    extension: 'js',
    directory: './seeds'
  }, this.config || {}, config);
};

module.exports = Seeder;
<|endoftext|>


<commit_before>var map = Ember.ArrayPolyfills.map;
var forEach = Ember.ArrayPolyfills.forEach;

DS.DjangoRESTSerializer = DS.RESTSerializer.extend({

  init: function() {
    this._super.apply(this, arguments);
  },

  /**
   * @method keyForType
   * @param {String} key
   * @returns String
   */
  keyForType: function(key) {
    return key + "_type";
  },

  /**
   * @method keyForEmbeddedType
   * @param {String} key
   * @returns String
   */
  keyForEmbeddedType: function(key) {
    return 'type';
  },

  extractDjangoPayload: function(store, type, payload) {
    type.eachRelationship(function(key, relationship){
      var embeddedTypeKey, isPolymorphic = false;
      if (relationship.options && relationship.options.polymorphic) {
        isPolymorphic = true;
      }

      if (!Ember.isNone(payload[key]) &&
          typeof(payload[key][0]) !== 'number' &&
            typeof(payload[key][0]) !== 'string' &&
              relationship.kind ==='hasMany') {
        if (Ember.typeOf(payload[key]) === 'array' && payload[key].length > 0) {
          if(isPolymorphic) {
            // If there is a hasMany polymorphic relationship, push each
            // item to the store individually, since they might not all
            // be the same type
            forEach.call(payload[key],function(hash) {
              var type = this.typeForRoot(hash.type);
              this.pushSinglePayload(store,type,hash);
            }, this);
          } else {
            var ids = payload[key].mapBy('id'); //todo find pk (not always id)
            this.pushArrayPayload(store, relationship.type, payload[key]);
            payload[key] = ids;
          }
        }
      }
      else if (!Ember.isNone(payload[key]) && typeof(payload[key]) === 'object' && relationship.kind ==='belongsTo') {
        var type = relationship.type;

        if(isPolymorphic) {
          type = this.typeForRoot(payload[key].type);
        }

        var id = payload[key].id;
        this.pushSinglePayload(store,type,payload[key]);

        if(!isPolymorphic) payload[key] = id;
      }
    }, this);
  },

  extractSingle: function(store, type, payload) {
    // using normalize from RESTSerializer applies transforms and allows
    // us to define keyForAttribute and keyForRelationship to handle
    // camelization correctly.
    this.normalize(type, payload);
    this.extractDjangoPayload(store, type, payload);
    return payload;
  },

  extractArray: function(store, type, payload) {
    var self = this;
    for (var j = 0; j < payload.length; j++) {
      // using normalize from RESTSerializer applies transforms and allows
      // us to define keyForAttribute and keyForRelationship to handle
      // camelization correctly.
      this.normalize(type, payload[j]);
      self.extractDjangoPayload(store, type, payload[j]);
    }
    return payload;
  },

  /**
   * This method allows you to push a single object payload.
   *
   * It will first normalize the payload, so you can use this to push
   * in data streaming in from your server structured the same way
   * that fetches and saves are structured.
   *
   * @param {DS.Store} store
   * @param {String} type
   * @param {Object} payload
   */
  pushSinglePayload: function(store, type, payload) {
    type = store.modelFor(type);
    payload = this.extract(store, type, payload, null, "find");
    store.push(type, payload);
  },

  /**
   * This method allows you to push an array of object payloads.
   *
   * It will first normalize the payload, so you can use this to push
   * in data streaming in from your server structured the same way
   * that fetches and saves are structured.
   *
   * @param {DS.Store} store
   * @param {String} type
   * @param {Object} payload
   */
  pushArrayPayload: function(store, type, payload) {
    type = store.modelFor(type);
    payload = this.extract(store, type, payload, null, "findAll");
    store.pushMany(type, payload);
  },

  /**
   * Converts camelcased attributes to underscored when serializing.
   *
   * Stolen from DS.ActiveModelSerializer.
   *
   * @method keyForAttribute
   * @param {String} attribute
   * @returns String
   */
  keyForAttribute: function(attr) {
    return Ember.String.decamelize(attr);
  },

  /**
   * Underscores relationship names when serializing relationship keys.
   *
   * Stolen from DS.ActiveModelSerializer.
   *
   * @method keyForRelationship
   * @param {String} key
   * @param {String} kind
   * @returns String
   */
  keyForRelationship: function(key, kind) {
    return Ember.String.decamelize(key);
  },

  /**
   * Adds support for skipping serialization of
   * DS.attr('foo', { readOnly: true })
   *
   * @method serializeAttribute
   */
  serializeAttribute: function(record, json, key, attribute) {
    if (!attribute.options.readOnly) {
      return this._super(record, json, key, attribute);
    }
  },

  /**
   * Underscore relationship names when serializing belongsToRelationships
   *
   * @method serializeBelongsTo
   */
  serializeBelongsTo: function(record, json, relationship) {
    var key = relationship.key;
    var belongsTo = record.get(key);
    var json_key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;

    if (Ember.isNone(belongsTo)) {
      json[json_key] = belongsTo;
    } else {
      if (typeof(record.get(key)) === 'string') {
        json[json_key] = record.get(key);
      }else{
        json[json_key] = record.get(key).get('id');
      }
    }

    if (relationship.options.polymorphic) {
      this.serializePolymorphicType(record, json, relationship);
    }
  },

  /**
   * Underscore relationship names when serializing hasManyRelationships
   *
   * @method serializeHasMany
   */
  serializeHasMany: function(record, json, relationship) {
    if (relationship.options.polymorphic) {
      // TODO implement once it's implemented in DS.JSONSerializer
      return;
    }

    var key = relationship.key,
    json_key = this.keyForRelationship(key, "hasMany"),
    relationshipType = DS.RelationshipChange.determineRelationshipType(
      record.constructor, relationship);

      if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') {
        json[json_key] = record.get(key).mapBy('id');
      }
  },

  /**
   * Add the key for a polymorphic relationship by adding `_type` to the
   * attribute and value from the model's underscored name.
   *
   * @method serializePolymorphicType
   * @param {DS.Model} record
   * @param {Object} json
   * @param {Object} relationship
   */
  serializePolymorphicType: function(record, json, relationship) {
    var key = relationship.key,
    belongsTo = Ember.get(record, key);
    key = this.keyForAttribute ? this.keyForAttribute(key) : key;
    if(belongsTo) {
      json[this.keyForType(key)] = Ember.String.underscore(belongsTo.constructor.typeKey);
    } else {
      json[this.keyForType(key)] = null;
    }
  },

  /**
   * Normalize:
   *
   * ```js
   * {
   *   minion: "1"
   *   minion_type: "evil_minion",
   *   author: {
   *     embeddedType: "user",
   *     id: 1
   *   }
   * }
   * ```
   *
   * To:
   *
   * ```js
   * {
   *   minion: "1"
   *   minionType: "evil_minion"
   *   author: {
   *     type: "user",
   *     id: 1
   *   }
   * }
   * ```
   * @method normalizeRelationships
   * @private
   */
  normalizeRelationships: function(type,hash) {
    this._super.apply(this, arguments);

    if (this.keyForRelationship) {
      type.eachRelationship(function(key, relationship) {
        if (relationship.options.polymorphic) {
          var typeKey = this.keyForType(relationship.key);
          if(hash[typeKey]) {
            var typeKeyCamelCase = typeKey.replace(/_type$/,'Type');
            hash[typeKeyCamelCase] = hash[typeKey];
            delete hash[typeKey];
          }

          if(hash[relationship.key]) {
            var embeddedData = hash[relationship.key];
            var embeddedTypeKey = this.keyForEmbeddedType(relationship.key);
            if(embeddedTypeKey !== 'type') {
              if(Ember.isArray(embeddedData) && embeddedData.length) {
                map.call(embeddedData, function(obj,i) {
                  this.normalizeTypeKey(obj,embeddedTypeKey);
                }, this);
              } else if(embeddedData[embeddedTypeKey]) {
                this.normalizeTypeKey(embeddedData,embeddedTypeKey);
              }
            }
          }
        }
      }, this);
    }
  },

  /**
   * Replace a custom type key with a key named `type`.
   *
   * @method normalizeTypeKey
   * @param {Object} obj
   * @param {String} key
   */
  normalizeTypeKey: function(obj,key) {
    obj.type = obj[key];
  }
});
<commit_msg>hasMany update for Ember Data 1.0.0-beta.9<commit_after>var map = Ember.ArrayPolyfills.map;
var forEach = Ember.ArrayPolyfills.forEach;

DS.DjangoRESTSerializer = DS.RESTSerializer.extend({

  init: function() {
    this._super.apply(this, arguments);
  },

  /**
   * @method keyForType
   * @param {String} key
   * @returns String
   */
  keyForType: function(key) {
    return key + "_type";
  },

  /**
   * @method keyForEmbeddedType
   * @param {String} key
   * @returns String
   */
  keyForEmbeddedType: function(key) {
    return 'type';
  },

  extractDjangoPayload: function(store, type, payload) {
    type.eachRelationship(function(key, relationship){
      var embeddedTypeKey, isPolymorphic = false;
      if (relationship.options && relationship.options.polymorphic) {
        isPolymorphic = true;
      }

      if (!Ember.isNone(payload[key]) &&
          typeof(payload[key][0]) !== 'number' &&
            typeof(payload[key][0]) !== 'string' &&
              relationship.kind ==='hasMany') {
        if (Ember.typeOf(payload[key]) === 'array' && payload[key].length > 0) {
          if(isPolymorphic) {
            // If there is a hasMany polymorphic relationship, push each
            // item to the store individually, since they might not all
            // be the same type
            forEach.call(payload[key],function(hash) {
              var type = this.typeForRoot(hash.type);
              this.pushSinglePayload(store,type,hash);
            }, this);
          } else {
            var ids = payload[key].mapBy('id'); //todo find pk (not always id)
            this.pushArrayPayload(store, relationship.type, payload[key]);
            payload[key] = ids;
          }
        }
      }
      else if (!Ember.isNone(payload[key]) && typeof(payload[key]) === 'object' && relationship.kind ==='belongsTo') {
        var type = relationship.type;

        if(isPolymorphic) {
          type = this.typeForRoot(payload[key].type);
        }

        var id = payload[key].id;
        this.pushSinglePayload(store,type,payload[key]);

        if(!isPolymorphic) payload[key] = id;
      }
    }, this);
  },

  extractSingle: function(store, type, payload) {
    // using normalize from RESTSerializer applies transforms and allows
    // us to define keyForAttribute and keyForRelationship to handle
    // camelization correctly.
    this.normalize(type, payload);
    this.extractDjangoPayload(store, type, payload);
    return payload;
  },

  extractArray: function(store, type, payload) {
    var self = this;
    for (var j = 0; j < payload.length; j++) {
      // using normalize from RESTSerializer applies transforms and allows
      // us to define keyForAttribute and keyForRelationship to handle
      // camelization correctly.
      this.normalize(type, payload[j]);
      self.extractDjangoPayload(store, type, payload[j]);
    }
    return payload;
  },

  /**
   * This method allows you to push a single object payload.
   *
   * It will first normalize the payload, so you can use this to push
   * in data streaming in from your server structured the same way
   * that fetches and saves are structured.
   *
   * @param {DS.Store} store
   * @param {String} type
   * @param {Object} payload
   */
  pushSinglePayload: function(store, type, payload) {
    type = store.modelFor(type);
    payload = this.extract(store, type, payload, null, "find");
    store.push(type, payload);
  },

  /**
   * This method allows you to push an array of object payloads.
   *
   * It will first normalize the payload, so you can use this to push
   * in data streaming in from your server structured the same way
   * that fetches and saves are structured.
   *
   * @param {DS.Store} store
   * @param {String} type
   * @param {Object} payload
   */
  pushArrayPayload: function(store, type, payload) {
    type = store.modelFor(type);
    payload = this.extract(store, type, payload, null, "findAll");
    store.pushMany(type, payload);
  },

  /**
   * Converts camelcased attributes to underscored when serializing.
   *
   * Stolen from DS.ActiveModelSerializer.
   *
   * @method keyForAttribute
   * @param {String} attribute
   * @returns String
   */
  keyForAttribute: function(attr) {
    return Ember.String.decamelize(attr);
  },

  /**
   * Underscores relationship names when serializing relationship keys.
   *
   * Stolen from DS.ActiveModelSerializer.
   *
   * @method keyForRelationship
   * @param {String} key
   * @param {String} kind
   * @returns String
   */
  keyForRelationship: function(key, kind) {
    return Ember.String.decamelize(key);
  },

  /**
   * Adds support for skipping serialization of
   * DS.attr('foo', { readOnly: true })
   *
   * @method serializeAttribute
   */
  serializeAttribute: function(record, json, key, attribute) {
    if (!attribute.options.readOnly) {
      return this._super(record, json, key, attribute);
    }
  },

  /**
   * Underscore relationship names when serializing belongsToRelationships
   *
   * @method serializeBelongsTo
   */
  serializeBelongsTo: function(record, json, relationship) {
    var key = relationship.key;
    var belongsTo = record.get(key);
    var json_key = this.keyForRelationship ? this.keyForRelationship(key, "belongsTo") : key;

    if (Ember.isNone(belongsTo)) {
      json[json_key] = belongsTo;
    } else {
      if (typeof(record.get(key)) === 'string') {
        json[json_key] = record.get(key);
      }else{
        json[json_key] = record.get(key).get('id');
      }
    }

    if (relationship.options.polymorphic) {
      this.serializePolymorphicType(record, json, relationship);
    }
  },

  /**
   * Underscore relationship names when serializing hasManyRelationships
   *
   * @method serializeHasMany
   */
  serializeHasMany: function(record, json, relationship) {
    if (relationship.options.polymorphic) {
      // TODO implement once it's implemented in DS.JSONSerializer
      return;
    }

    var key = relationship.key,
    json_key = this.keyForRelationship(key, "hasMany"),
    relationshipType = record.constructor.determineRelationshipType(relationship);

      if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany') {
        json[json_key] = record.get(key).mapBy('id');
      }
  },

  /**
   * Add the key for a polymorphic relationship by adding `_type` to the
   * attribute and value from the model's underscored name.
   *
   * @method serializePolymorphicType
   * @param {DS.Model} record
   * @param {Object} json
   * @param {Object} relationship
   */
  serializePolymorphicType: function(record, json, relationship) {
    var key = relationship.key,
    belongsTo = Ember.get(record, key);
    key = this.keyForAttribute ? this.keyForAttribute(key) : key;
    if(belongsTo) {
      json[this.keyForType(key)] = Ember.String.underscore(belongsTo.constructor.typeKey);
    } else {
      json[this.keyForType(key)] = null;
    }
  },

  /**
   * Normalize:
   *
   * ```js
   * {
   *   minion: "1"
   *   minion_type: "evil_minion",
   *   author: {
   *     embeddedType: "user",
   *     id: 1
   *   }
   * }
   * ```
   *
   * To:
   *
   * ```js
   * {
   *   minion: "1"
   *   minionType: "evil_minion"
   *   author: {
   *     type: "user",
   *     id: 1
   *   }
   * }
   * ```
   * @method normalizeRelationships
   * @private
   */
  normalizeRelationships: function(type,hash) {
    this._super.apply(this, arguments);

    if (this.keyForRelationship) {
      type.eachRelationship(function(key, relationship) {
        if (relationship.options.polymorphic) {
          var typeKey = this.keyForType(relationship.key);
          if(hash[typeKey]) {
            var typeKeyCamelCase = typeKey.replace(/_type$/,'Type');
            hash[typeKeyCamelCase] = hash[typeKey];
            delete hash[typeKey];
          }

          if(hash[relationship.key]) {
            var embeddedData = hash[relationship.key];
            var embeddedTypeKey = this.keyForEmbeddedType(relationship.key);
            if(embeddedTypeKey !== 'type') {
              if(Ember.isArray(embeddedData) && embeddedData.length) {
                map.call(embeddedData, function(obj,i) {
                  this.normalizeTypeKey(obj,embeddedTypeKey);
                }, this);
              } else if(embeddedData[embeddedTypeKey]) {
                this.normalizeTypeKey(embeddedData,embeddedTypeKey);
              }
            }
          }
        }
      }, this);
    }
  },

  /**
   * Replace a custom type key with a key named `type`.
   *
   * @method normalizeTypeKey
   * @param {Object} obj
   * @param {String} key
   */
  normalizeTypeKey: function(obj,key) {
    obj.type = obj[key];
  }
});
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const fs = require('fs');
const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const config = require('./config');

const { log, LogLevel } = require('./log');

const users = require('./routes/users');
const fileProcessing = require('./routes/fileProcessing');
const readings = require('./routes/readings');
const meters = require('./routes/meters');
const preferences = require('./routes/preferences');
const login = require('./routes/login');
const verification = require('./routes/verification');
const groups = require('./routes/groups');
const version = require('./routes/version');
const createRouterForNewCompressedReadings = require('./routes/compressedReadings').createRouter;
const createRouterForCompareReadings = require('./routes/compareReadings').createRouter;
const baseline = require('./routes/baseline');

const app = express();

// If other logging is turned off, there's no reason to log HTTP requests either.
// TODO: Potentially modify the Morgan logger to use the log API, thus unifying all our logging.
if (log.level !== LogLevel.SILENT) {
	app.use(logger('dev'));
}

app.use(favicon(path.join(__dirname, '..', 'client', 'public', 'favicon.ico')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

app.use('/api/users', users);
app.use('/api/meters', meters);
app.use('/api/readings', readings);
app.use('/api/preferences', preferences);
app.use('/api/login', login);
app.use('/api/groups', groups);
app.use('/api/verification', verification);
app.use('/api/fileProcessing', fileProcessing);
app.use('/api/version', version);
app.use('/api/compressedReadings', createRouterForNewCompressedReadings());
app.use('/api/compareReadings', createRouterForCompareReadings());
app.use('/api/baselines', baseline);
app.use(express.static(path.join(__dirname, '..', 'client', 'public')));

const router = express.Router();

router.get(/^(\/)(login|admin|groups|createGroup|editGroup|graph|meters|editMeter)?$/, (req, res) => {
	fs.readFile(path.resolve(__dirname, '..', 'client', 'index.html'), (err, html) => {
		let htmlPlusData = html.toString().replace('SUBDIR', config.subdir);
		res.send(htmlPlusData);
	});
});

app.use(router);

app.use((req, res) => {
	res.status(404).send('
404 Not Found
');
});

module.exports = app;
<commit_msg>Fix bug where chart links went to a blank page<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const fs = require('fs');
const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const config = require('./config');

const { log, LogLevel } = require('./log');

const users = require('./routes/users');
const fileProcessing = require('./routes/fileProcessing');
const readings = require('./routes/readings');
const meters = require('./routes/meters');
const preferences = require('./routes/preferences');
const login = require('./routes/login');
const verification = require('./routes/verification');
const groups = require('./routes/groups');
const version = require('./routes/version');
const createRouterForNewCompressedReadings = require('./routes/compressedReadings').createRouter;
const createRouterForCompareReadings = require('./routes/compareReadings').createRouter;
const baseline = require('./routes/baseline');

const app = express();

// If other logging is turned off, there's no reason to log HTTP requests either.
// TODO: Potentially modify the Morgan logger to use the log API, thus unifying all our logging.
if (log.level !== LogLevel.SILENT) {
	app.use(logger('dev'));
}

app.use(favicon(path.join(__dirname, '..', 'client', 'public', 'favicon.ico')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());

app.use('/api/users', users);
app.use('/api/meters', meters);
app.use('/api/readings', readings);
app.use('/api/preferences', preferences);
app.use('/api/login', login);
app.use('/api/groups', groups);
app.use('/api/verification', verification);
app.use('/api/fileProcessing', fileProcessing);
app.use('/api/version', version);
app.use('/api/compressedReadings', createRouterForNewCompressedReadings());
app.use('/api/compareReadings', createRouterForCompareReadings());
app.use('/api/baselines', baseline);
app.use(express.static(path.join(__dirname, '..', 'client', 'public')));

const router = express.Router();

router.get(/^(\/)(login|admin|groups|createGroup|editGroup|graph|meters|editMeter)?$/, (req, res) => {
	fs.readFile(path.resolve(__dirname, '..', 'client', 'index.html'), (err, html) => {
		const subdir = config.subdir || "/";
		let htmlPlusData = html.toString().replace('SUBDIR', subdir);
		res.send(htmlPlusData);
	});
});

app.use(router);

app.use((req, res) => {
	res.status(404).send('
404 Not Found
');
});

module.exports = app;
<|endoftext|>