<commit_before>/**
 * @module og/terrainProvider/GlobusTerrain
 */

'use strict';

import * as mercator from '../mercator.js';
//import { cartesianToBarycentricLonLat } from '../utils/shared.js';
import { EmptyTerrain } from './EmptyTerrain.js';
import { EPSG3857 } from '../proj/EPSG3857.js';
import { Events } from '../Events.js';
import { Loader } from '../utils/Loader.js';
import { NOTRENDERING } from '../quadTree/quadTree.js';
import { QueueArray } from '../QueueArray.js';
import { stringTemplate } from '../utils/shared.js';
import { Geoid } from './Geoid.js';
import { Layer } from '../layer/Layer.js';
import { Vec2 } from '../math/Vec2.js';
import { Vec3 } from '../math/Vec3.js';
import { Extent } from '../Extent.js';
import { LonLat } from '../LonLat.js';
import { Ray } from '../math/Ray.js';

const EVENT_NAMES = [
    /**
    * Triggered when current elevation tile has loaded but before rendereing.
    * @event og.terrain.GlobusTerrain#load
    */
    "load",

    /**
    * Triggered when all elevation tiles have loaded or loading has stopped.
    * @event og.terrain.GlobusTerrain#loadend
    */
    "loadend"
];

/**
 * Class that loads segment elevation data, converts it to the array and passes it to the planet segment.
 * @class
 * @extends {og.terrain.EmptyTerrain}
 * @param {string} [name=""] - Terrain provider name.
 * @param {Object} [options] - Provider options:
 * @param {number} [options.minZoom=3] - Minimal visible zoom index when terrain handler works.
 * @param {number} [options.minZoom=14] - Maximal visible zoom index when terrain handler works.
 * @param {string} [options.url="//openglobus.org/heights/srtm3/{z}/{y}/{x}.ddm"] - Terrain source path url template. Default is openglobus ddm elevation file.
 * @param {Array.<number>} [options.gridSizeByZoom] - Array of segment triangulation grid sizes where array index agreed to the segment zoom index.
 * @param {number} [options.fileGridSize=32] - Elevation tile grid size. Default is 32x32.
 * @param {string} [options.responseType="arraybuffer"] - Ajax responce type.
 * @param {number} [options.MAX_LOADING_TILES] - Maximum at one time loading tiles.
 * @fires og.terrain.GlobusTerrain#load
 * @fires og.terrain.GlobusTerrain#loadend
 */
class GlobusTerrain extends EmptyTerrain {
    constructor(name, options) {

        super();

        options = options || {};

        this.blur = true;
        this.equalizeNormals = true;

        //this.blur = false;
        //this.equalizeNormals = false;

        /**
         * Provider name.
         * @public
         * @type {string}
         */
        this.name = name || "";

        /**
         * Minimal visible zoom index when terrain handler works.
         * @public
         * @type {number}
         */
        this.minZoom = options.minZoom || 3;

        /**
         * Maximal visible zoom index when terrain handler works.
         * @public
         * @type {number}
         */
        this.maxZoom = options.maxZoom || 14;

        this._geoid = new Geoid({
            'src': "//openglobus.org/geoid/egm96-15.pgm"
        });

        /**
         * Terrain source path url template.
         * @public
         * @type {string}
         */
        this.url = options.url || "//openglobus.org/heights/srtm3/{z}/{y}/{x}.ddm";

        this._dataType = "arrayBuffer";

        /**
         * Array of segment triangulation grid sizes where array index agreed to the segment zoom index.
         * @public
         * @type {Array.<number>}
         */
        //this.gridSizeByZoom = options.gridSizeByZoom || [64, 32, 32, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 32, 32, 16, 8, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2];
        this.gridSizeByZoom = options.gridSizeByZoom || [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 64, 32, 16, 8, 4, 2, 2, 2, 2, 2, 2];

        this._maxNodeZoom = this.gridSizeByZoom.length - 1;

        /**
         * Elevation tile grid size.
         * @public
         * @type {number}
         */
        this.fileGridSize = options.fileGridSize || 32;

        /**
         * Events handler.
         * @public
         * @type {og.Events}
         */
        this.events = new Events(EVENT_NAMES, this);

        this._elevationCache = {};

        this._fetchCache = {};

        this._loader = new Loader();

        /**
         * Rewrites elevation storage url query.
         * @private
         * @callback og.terrain.GlobusTerrain~_urlRewriteCallback
         * @param {og.planetSegment.Segment} segment - Segment to load.
         * @param {string} url - Created url.
         * @returns {string} - Url query string.
         */
        this._urlRewriteCallback = null;
    }

    getHeightAsync(lonLat, callback, zoom) {

        if (!lonLat || lonLat.lat > mercator.MAX_LAT || lonLat.lat < mercator.MIN_LAT) {
            callback(0);
            return true;
        }

        let z = zoom || this.maxZoom,
            z2 = Math.pow(2, z),
            size = mercator.POLE2 / z2,
            merc = mercator.forward(lonLat),
            x = Math.floor((mercator.POLE + merc.lon) / size),
            y = Math.floor((mercator.POLE - merc.lat) / size);

        let tileIndex = Layer.getTileIndex(x, y, z);

        let cache = this._elevationCache[tileIndex];

        if (cache) {
            if (!cache.heights) {
                callback(this._geoid.getHeightLonLat(lonLat));
            } else {
                callback(this._getGroundHeightMerc(merc, cache));
            }
            return true;
        } else {

            if (!this._fetchCache[tileIndex]) {
                let url = stringTemplate(this.url, {
                    "x": x,
                    "y": y,
                    "z": z
                });
                this._fetchCache[tileIndex] = this._loader.fetch({
                    'src': url,
                    'type': this._dataType
                });
            }

            this._fetchCache[tileIndex].then((response) => {
                if (response.status === "ready") {
                    let cache = {
                        heights: this._createHeights(response.data),
                        extent: mercator.getTileExtent(x, y, z)
                    };
                    this._elevationCache[tileIndex] = cache;
                    callback(this._getGroundHeightMerc(merc, cache));
                } else if (response.status === "error") {
                    let cache = {
                        heights: null,
                        extent: mercator.getTileExtent(x, y, z)
                    };
                    this._elevationCache[tileIndex] = cache;
                    callback(this._geoid.getHeightLonLat(lonLat));
                } else {
                    this._fetchCache[tileIndex] = null;
                    delete this._fetchCache[tileIndex];
                }
            });
        }

        return false;
    }

    getTileCache(lonLat, z) {

        if (!lonLat || lonLat.lat > mercator.MAX_LAT || lonLat.lat < mercator.MIN_LAT) {
            return;
        }

        let z2 = Math.pow(2, z),
            size = mercator.POLE2 / z2,
            merc = mercator.forward(lonLat),
            x = Math.floor((mercator.POLE + merc.lon) / size),
            y = Math.floor((mercator.POLE - merc.lat) / size);

        let tileIndex = Layer.getTileIndex(x, y, z);

        return this._elevationCache[tileIndex];
    }

    _getGroundHeightMerc(merc, tileData) {

        if (!(tileData.extent && tileData.heights)) {
            return 0;
        }

        let w = tileData.extent.getWidth(),
            gs = Math.sqrt(tileData.heights.length);

        if (!tileData.extent.isInside(merc)) {
            console.log("GlobusTerrain.js 221 - error!");
            debugger;
        }

        let size = w / (gs - 1);

        /*
        v2-----------v3
        |            |
        |            |
        |            |
        v0-----------v1
        */

        let i = gs - Math.ceil((merc.lat - tileData.extent.southWest.lat) / size) - 1,
            j = Math.floor((merc.lon - tileData.extent.southWest.lon) / size);

        let v0Ind = (i + 1) * gs + j,
            v1Ind = v0Ind + 1,
            v2Ind = i * gs + j,
            v3Ind = v2Ind + 1;

        let h0 = tileData.heights[v0Ind],
            h1 = tileData.heights[v1Ind],
            h2 = tileData.heights[v2Ind],
            h3 = tileData.heights[v3Ind];

        let v0 = new Vec3(tileData.extent.southWest.lon + size * j, h0, tileData.extent.northEast.lat - size * i - size),
            v1 = new Vec3(v0.x + size, h1, v0.z),
            v2 = new Vec3(v0.x, h2, v0.z + size),
            v3 = new Vec3(v0.x + size, h3, v0.z + size);

        let xyz = new Vec3(merc.lon, 100000.0, merc.lat),
            ray = new Ray(xyz, new Vec3(0, -1, 0));

        let res = new Vec3();
        let d = ray.hitTriangle(v0, v1, v2, res);

        if (d === Ray.INSIDE) {
            return res.y;
        }

        d = ray.hitTriangle(v1, v3, v2, res);
        if (d === Ray.INSIDE) {
            return res.y;
        }

        if (d === Ray.AWAY) {
            console.log("GlobusTerrain.js 337 - error!");
            debugger;
        }
    }

    /**
     * Stop loading.
     * @public
     */
    abortLoading() {
        this._loader.abort();
    }

    /**
     * Sets terrain data url template.
     * @public
     * @param {string} url - Url template.
     * @example <caption>Default openglobus url template:</caption>:
     * "http://earth3.openglobus.org/{z}/{y}/{x}.ddm"
     */
    setUrl(url) {
        this.url = url;
    }

    /**
     * Sets provider name.
     * @public
     * @param {string} name - Name.
     */
    setName(name) {
        this.name = name;
    }

    /**
     * Starts to load segment data.
     * @public
     * @virtual
     * @param {og.planetSegment.Segment} segment - Segment that wants a terrain data.
     */
    loadTerrain(segment, forceLoading) {
        if (this._planet.terrainLock.isFree()) {
            segment.terrainReady = false;
            segment.terrainIsLoading = true;
            if (segment._projection.id === EPSG3857.id) {
                let cache = this._elevationCache[segment.tileIndex];
                if (cache) {
                    this._applyElevationsData(segment, cache.heights);
                } else {

                    this._loader.load({
                        'src': this._getHTTPRequestString(segment),
                        'segment': segment,
                        'type': this._dataType,
                        'filter': () => segment.plainReady && segment.node.getState() !== NOTRENDERING || forceLoading
                    }, response => {
                        if (response.status === "ready") {
                            let heights = this._createHeights(response.data, segment);
                            this._elevationCache[segment.tileIndex] = {
                                heights: heights,
                                extent: segment.getExtent()
                            };
                            this._applyElevationsData(segment, heights);
                        } else if (response.status === "abort") {
                            segment.terrainIsLoading = false;
                        } else if (response.status === "error") {
                            this._applyElevationsData(segment, null);
                        } else {
                            segment.terrainIsLoading = false;
                        }
                    });
                }
            } else {
                //TODO: poles elevation
            }
        } else {
            segment.terrainIsLoading = false;
        }
    }

    /**
     * Creates query url.
     * @protected
     * @virtual
     * @param {og.planetSegment.Segment} segment -
     * @returns {string} -
     */
    _createUrl(segment) {
        return stringTemplate(this.url, {
            "x": segment.tileX.toString(),
            "y": segment.tileY.toString(),
            "z": segment.tileZoom.toString()
        });
    }

    /**
     * Returns actual url query string.
     * @protected
     * @param {og.planetSegment.Segment} segment - Segment that loads elevation data.
     * @returns {string} -
     */
    _getHTTPRequestString(segment) {
        var url = this._createUrl(segment);
        return this._urlRewriteCallback ? this._urlRewriteCallback(segment, url) : url;
    }

    /**
     * Sets url rewrite callback, used for custom url rewriting for every tile laoding.
     * @public
     * @param {og.terrain.GlobusTerrain~_urlRewriteCallback} ur - The callback that returns tile custom created url.
     */
    setUrlRewriteCallback(ur) {
        this._urlRewriteCallback = ur;
    }

    /**
     * Converts loaded data to segment elevation data type(columr major elevation data array in meters)
     * @public
     * @virtual
     * @param {*} data - Loaded elevation data.
     * @returns {Array.<number>} -
     */
    _createHeights(data) {
        return new Float32Array(data);
    }

    /**
     * @protected
     * @param {og.planetSegment.Segment} segment -
     * @param {*} data -
     */
    _applyElevationsData(segment, elevations) {
        if (segment) {
            var e = this.events.load;
            if (e.handlers.length) {
                this.events.dispatch(e, {
                    "elevations": elevations,
                    "segment": segment
                });
            }
            segment.applyTerrain(elevations);
        }
    }
}

export { GlobusTerrain };
<commit_msg>Update og.<commit_after>/**
 * @module og/terrainProvider/GlobusTerrain
 */

'use strict';

import * as mercator from '../mercator.js';
//import { cartesianToBarycentricLonLat } from '../utils/shared.js';
import { EmptyTerrain } from './EmptyTerrain.js';
import { EPSG3857 } from '../proj/EPSG3857.js';
import { Events } from '../Events.js';
import { Loader } from '../utils/Loader.js';
import { NOTRENDERING } from '../quadTree/quadTree.js';
import { QueueArray } from '../QueueArray.js';
import { stringTemplate } from '../utils/shared.js';
import { Geoid } from './Geoid.js';
import { Layer } from '../layer/Layer.js';
import { Vec2 } from '../math/Vec2.js';
import { Vec3 } from '../math/Vec3.js';
import { Extent } from '../Extent.js';
import { LonLat } from '../LonLat.js';
import { Ray } from '../math/Ray.js';

const EVENT_NAMES = [
    /**
    * Triggered when current elevation tile has loaded but before rendereing.
    * @event og.terrain.GlobusTerrain#load
    */
    "load",

    /**
    * Triggered when all elevation tiles have loaded or loading has stopped.
    * @event og.terrain.GlobusTerrain#loadend
    */
    "loadend"
];

/**
 * Class that loads segment elevation data, converts it to the array and passes it to the planet segment.
 * @class
 * @extends {og.terrain.EmptyTerrain}
 * @param {string} [name=""] - Terrain provider name.
 * @param {Object} [options] - Provider options:
 * @param {number} [options.minZoom=3] - Minimal visible zoom index when terrain handler works.
 * @param {number} [options.minZoom=14] - Maximal visible zoom index when terrain handler works.
 * @param {string} [options.url="//openglobus.org/heights/srtm3/{z}/{y}/{x}.ddm"] - Terrain source path url template. Default is openglobus ddm elevation file.
 * @param {Array.<number>} [options.gridSizeByZoom] - Array of segment triangulation grid sizes where array index agreed to the segment zoom index.
 * @param {number} [options.fileGridSize=32] - Elevation tile grid size. Default is 32x32.
 * @param {string} [options.responseType="arraybuffer"] - Ajax responce type.
 * @param {number} [options.MAX_LOADING_TILES] - Maximum at one time loading tiles.
 * @fires og.terrain.GlobusTerrain#load
 * @fires og.terrain.GlobusTerrain#loadend
 */
class GlobusTerrain extends EmptyTerrain {
    constructor(name, options) {

        super();

        options = options || {};

        this.blur = true;
        this.equalizeNormals = true;

        //this.blur = false;
        //this.equalizeNormals = false;

        /**
         * Provider name.
         * @public
         * @type {string}
         */
        this.name = name || "";

        /**
         * Minimal visible zoom index when terrain handler works.
         * @public
         * @type {number}
         */
        this.minZoom = options.minZoom || 3;

        /**
         * Maximal visible zoom index when terrain handler works.
         * @public
         * @type {number}
         */
        this.maxZoom = options.maxZoom || 14;

        this._geoid = new Geoid({
            'src': "//openglobus.org/geoid/egm96-15.pgm"
        });

        /**
         * Terrain source path url template.
         * @public
         * @type {string}
         */
        this.url = options.url || "//openglobus.org/heights/srtm3/{z}/{y}/{x}.ddm";

        this._dataType = "arrayBuffer";

        /**
         * Array of segment triangulation grid sizes where array index agreed to the segment zoom index.
         * @public
         * @type {Array.<number>}
         */
        this.gridSizeByZoom = options.gridSizeByZoom || [64, 32, 32, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 32, 32, 16, 8, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2];
        //this.gridSizeByZoom = options.gridSizeByZoom || [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 64, 32, 16, 8, 4, 2, 2, 2, 2, 2, 2];

        this._maxNodeZoom = this.gridSizeByZoom.length - 1;

        /**
         * Elevation tile grid size.
         * @public
         * @type {number}
         */
        this.fileGridSize = options.fileGridSize || 32;

        /**
         * Events handler.
         * @public
         * @type {og.Events}
         */
        this.events = new Events(EVENT_NAMES, this);

        this._elevationCache = {};

        this._fetchCache = {};

        this._loader = new Loader();

        /**
         * Rewrites elevation storage url query.
         * @private
         * @callback og.terrain.GlobusTerrain~_urlRewriteCallback
         * @param {og.planetSegment.Segment} segment - Segment to load.
         * @param {string} url - Created url.
         * @returns {string} - Url query string.
         */
        this._urlRewriteCallback = null;
    }

    getHeightAsync(lonLat, callback, zoom) {

        if (!lonLat || lonLat.lat > mercator.MAX_LAT || lonLat.lat < mercator.MIN_LAT) {
            callback(0);
            return true;
        }

        let z = zoom || this.maxZoom,
            z2 = Math.pow(2, z),
            size = mercator.POLE2 / z2,
            merc = mercator.forward(lonLat),
            x = Math.floor((mercator.POLE + merc.lon) / size),
            y = Math.floor((mercator.POLE - merc.lat) / size);

        let tileIndex = Layer.getTileIndex(x, y, z);

        let cache = this._elevationCache[tileIndex];

        if (cache) {
            if (!cache.heights) {
                callback(this._geoid.getHeightLonLat(lonLat));
            } else {
                callback(this._getGroundHeightMerc(merc, cache));
            }
            return true;
        } else {

            if (!this._fetchCache[tileIndex]) {
                let url = stringTemplate(this.url, {
                    "x": x,
                    "y": y,
                    "z": z
                });
                this._fetchCache[tileIndex] = this._loader.fetch({
                    'src': url,
                    'type': this._dataType
                });
            }

            this._fetchCache[tileIndex].then((response) => {
                if (response.status === "ready") {
                    let cache = {
                        heights: this._createHeights(response.data),
                        extent: mercator.getTileExtent(x, y, z)
                    };
                    this._elevationCache[tileIndex] = cache;
                    callback(this._getGroundHeightMerc(merc, cache));
                } else if (response.status === "error") {
                    let cache = {
                        heights: null,
                        extent: mercator.getTileExtent(x, y, z)
                    };
                    this._elevationCache[tileIndex] = cache;
                    callback(this._geoid.getHeightLonLat(lonLat));
                } else {
                    this._fetchCache[tileIndex] = null;
                    delete this._fetchCache[tileIndex];
                }
            });
        }

        return false;
    }

    getTileCache(lonLat, z) {

        if (!lonLat || lonLat.lat > mercator.MAX_LAT || lonLat.lat < mercator.MIN_LAT) {
            return;
        }

        let z2 = Math.pow(2, z),
            size = mercator.POLE2 / z2,
            merc = mercator.forward(lonLat),
            x = Math.floor((mercator.POLE + merc.lon) / size),
            y = Math.floor((mercator.POLE - merc.lat) / size);

        let tileIndex = Layer.getTileIndex(x, y, z);

        return this._elevationCache[tileIndex];
    }

    _getGroundHeightMerc(merc, tileData) {

        if (!(tileData.extent && tileData.heights)) {
            return 0;
        }

        let w = tileData.extent.getWidth(),
            gs = Math.sqrt(tileData.heights.length);

        if (!tileData.extent.isInside(merc)) {
            console.log("GlobusTerrain.js 221 - error!");
            debugger;
        }

        let size = w / (gs - 1);

        /*
        v2-----------v3
        |            |
        |            |
        |            |
        v0-----------v1
        */

        let i = gs - Math.ceil((merc.lat - tileData.extent.southWest.lat) / size) - 1,
            j = Math.floor((merc.lon - tileData.extent.southWest.lon) / size);

        let v0Ind = (i + 1) * gs + j,
            v1Ind = v0Ind + 1,
            v2Ind = i * gs + j,
            v3Ind = v2Ind + 1;

        let h0 = tileData.heights[v0Ind],
            h1 = tileData.heights[v1Ind],
            h2 = tileData.heights[v2Ind],
            h3 = tileData.heights[v3Ind];

        let v0 = new Vec3(tileData.extent.southWest.lon + size * j, h0, tileData.extent.northEast.lat - size * i - size),
            v1 = new Vec3(v0.x + size, h1, v0.z),
            v2 = new Vec3(v0.x, h2, v0.z + size),
            v3 = new Vec3(v0.x + size, h3, v0.z + size);

        let xyz = new Vec3(merc.lon, 100000.0, merc.lat),
            ray = new Ray(xyz, new Vec3(0, -1, 0));

        let res = new Vec3();
        let d = ray.hitTriangle(v0, v1, v2, res);

        if (d === Ray.INSIDE) {
            return res.y;
        }

        d = ray.hitTriangle(v1, v3, v2, res);
        if (d === Ray.INSIDE) {
            return res.y;
        }

        if (d === Ray.AWAY) {
            console.log("GlobusTerrain.js 337 - error!");
            debugger;
        }
    }

    /**
     * Stop loading.
     * @public
     */
    abortLoading() {
        this._loader.abort();
    }

    /**
     * Sets terrain data url template.
     * @public
     * @param {string} url - Url template.
     * @example <caption>Default openglobus url template:</caption>:
     * "http://earth3.openglobus.org/{z}/{y}/{x}.ddm"
     */
    setUrl(url) {
        this.url = url;
    }

    /**
     * Sets provider name.
     * @public
     * @param {string} name - Name.
     */
    setName(name) {
        this.name = name;
    }

    /**
     * Starts to load segment data.
     * @public
     * @virtual
     * @param {og.planetSegment.Segment} segment - Segment that wants a terrain data.
     */
    loadTerrain(segment, forceLoading) {
        if (this._planet.terrainLock.isFree()) {
            segment.terrainReady = false;
            segment.terrainIsLoading = true;
            if (segment._projection.id === EPSG3857.id) {
                let cache = this._elevationCache[segment.tileIndex];
                if (cache) {
                    this._applyElevationsData(segment, cache.heights);
                } else {

                    this._loader.load({
                        'src': this._getHTTPRequestString(segment),
                        'segment': segment,
                        'type': this._dataType,
                        'filter': () => segment.plainReady && segment.node.getState() !== NOTRENDERING || forceLoading
                    }, response => {
                        if (response.status === "ready") {
                            let heights = this._createHeights(response.data, segment);
                            this._elevationCache[segment.tileIndex] = {
                                heights: heights,
                                extent: segment.getExtent()
                            };
                            this._applyElevationsData(segment, heights);
                        } else if (response.status === "abort") {
                            segment.terrainIsLoading = false;
                        } else if (response.status === "error") {
                            this._applyElevationsData(segment, null);
                        } else {
                            segment.terrainIsLoading = false;
                        }
                    });
                }
            } else {
                //TODO: poles elevation
            }
        } else {
            segment.terrainIsLoading = false;
        }
    }

    /**
     * Creates query url.
     * @protected
     * @virtual
     * @param {og.planetSegment.Segment} segment -
     * @returns {string} -
     */
    _createUrl(segment) {
        return stringTemplate(this.url, {
            "x": segment.tileX.toString(),
            "y": segment.tileY.toString(),
            "z": segment.tileZoom.toString()
        });
    }

    /**
     * Returns actual url query string.
     * @protected
     * @param {og.planetSegment.Segment} segment - Segment that loads elevation data.
     * @returns {string} -
     */
    _getHTTPRequestString(segment) {
        var url = this._createUrl(segment);
        return this._urlRewriteCallback ? this._urlRewriteCallback(segment, url) : url;
    }

    /**
     * Sets url rewrite callback, used for custom url rewriting for every tile laoding.
     * @public
     * @param {og.terrain.GlobusTerrain~_urlRewriteCallback} ur - The callback that returns tile custom created url.
     */
    setUrlRewriteCallback(ur) {
        this._urlRewriteCallback = ur;
    }

    /**
     * Converts loaded data to segment elevation data type(columr major elevation data array in meters)
     * @public
     * @virtual
     * @param {*} data - Loaded elevation data.
     * @returns {Array.<number>} -
     */
    _createHeights(data) {
        return new Float32Array(data);
    }

    /**
     * @protected
     * @param {og.planetSegment.Segment} segment -
     * @param {*} data -
     */
    _applyElevationsData(segment, elevations) {
        if (segment) {
            var e = this.events.load;
            if (e.handlers.length) {
                this.events.dispatch(e, {
                    "elevations": elevations,
                    "segment": segment
                });
            }
            segment.applyTerrain(elevations);
        }
    }
}

export { GlobusTerrain };
<|endoftext|>


<commit_before>'use strict'
class Ball {
  constructor(args){
    this.x     = args.x;
    this.y     = args.y;
    this.rad   = args.rad;
    this.vec   = args.vec;
    this.color = "rgba(0,0,0,1)";
  }

  timeStep(){
    this.x = this.x+this.vec.x;
    this.y = this.y+this.vec.y;
  }

  drawable(ctx){
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.lineWidth = 0;
    ctx.strokeWidth = 0;
    ctx.strokeStyle = 'rgba(0,0,0,0)';
    ctx.stroke();
  }
}

class Paddle {
  constructor(args){
    this.length    = 100;
    this.width     = 5;
    this.x         = args.x;
    this.y         = args.y;
    this.totalH    = args.totalH;
    this.direction = 0;
    this.speed     = args.speed;
    this.color     = "rgba(0,0,0,1)";
  }

  timeStep(){
    if(this.y > 0 && this.direction < 0)
      this.y += this.direction*this.speed;
    else if(this.y < this.totalH-this.length && this.direction > 0)
      this.y += this.direction*this.speed;
  }

  drawable(ctx){
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x,this.y,this.width,this.length);
  }
}

class Canvas {
  constructor(args){
    this.canvasElem = args.canvasElem;
    this.ctx = args.canvasElem.getContext('2d');
    this.rect = this.canvasElem.getBoundingClientRect();
    this.width = this.rect.width;
    this.height = this.rect.height;
    this.canvasElem.width = this.width;
    this.canvasElem.height = this.height;
    this.backgroundColor = args.backgroundColor;
    this.forgroundColor = args.forgroundColor;
  }

  clear(){
    this.ctx.beginPath();
    this.ctx.fillStyle = this.backgroundColor;
    this.ctx.fillRect(0,0,this.width,this.height);
    this.ctx.strokeStyle = this.forgroundColor;
    this.ctx.strokeWidth = 2;
    this.ctx.rect(0,0,this.width,this.height);
    this.ctx.stroke();
    this.ctx.beginPath();
    this.ctx.moveTo(this.width/2,0);
    this.ctx.lineTo(this.width/2,this.height);
    this.ctx.stroke();
  }
}

class Controller{
  constructor(args){
    this.sampleFreq = args.sampleFreq;
    this.timestamp = Date.now();
    this.direction = 0;
    this.up = false;
    this.down = false;

    this.intervalFunc = (now)=>{
      if((now - this.timestamp) > this.sampleFreq){
        this.direction = 0;
      }
    };
  }
}

class KeyboardController extends Controller{
  constructor(args){
    super(args);
    console.log(this.direction);
    document.onkeypress = (e)=>{
      e = e || window.event;
      var charCode = (typeof e.which == "number") ? e.which : e.keyCode;
      if (charCode) {
        if(charCode == 119){ //'w'
          this.direction = -1;
          this.timestamp = Date.now();
        }
        if(charCode == 115){ //'s'
          this.direction = 1;
          this.timestamp = Date.now();
        }
      }
    };
  }
}

class LeapController extends Controller{
  constructor(args){
    super(args);

    const controllerOptions = {enableGestures: true};
    let setDirection = (dir) => {
                         this.direction = dir
                         this.timestamp = Date.now();
                        };

    //console.log("Starting Loop");
    Leap.loop(controllerOptions, function(frame) {
      //var interactionBox = frame.interactionBox;

      if (frame.hands.length > 0) {
        for (let i = 0; i < frame.hands.length; i++) {
          let hand = frame.hands[i];
          if(hand.indexFinger.extended){
            let nameMap = ["thumb", "index", "middle", "ring", "pinky"];
            let pointer = null;
            hand.pointables.forEach(function(pointable){
              if(nameMap[pointable.type] == "index") pointer = pointable;
            });
            if(pointer){
              let direction = pointer.direction;
              if(direction[1] > 0.3){
                setDirection(-1);
              }
              if(direction[1] < -0.3){
                setDirection(1);
              }
            }
          }
        }
      }
    });
  }

}

class AIController extends Controller{
  constructor(args){
    super(args);
  }
}

class Pong {
  constructor(canvas){
    this.framerate       = 20;
    this.inPlay          = true;
    this.canvas          = new Canvas({canvasElem:canvas,
                                       backgroundColor: "rgba(255,255,255,1)",
                                       forgroundColor: "rgba(0,0,0,1)"
                           });

    this.ball            = new Ball({x:this.canvas.width/2,
                                      y:this.canvas.height/2,
                                      rad:5,
                                      vec:{x:5,y:0},
                                      color:"rgba(0,0,0,1)"
                            });

    this.leftPaddle      = new Paddle({x:10,
                                       y:(canvas.height/2)-50,
                                       speed: 4,
                                       totalH: this.canvas.height
                            });

    this.rightPaddle     = new Paddle({x:canvas.width-10,
                                       y:(canvas.height/2)-50,
                                       speed: 4,
                                       totalH: this.canvas.height
                            });

    this.leftController  = new AIController({sampleFreq: this.framerate});

    this.rightController = new LeapController({sampleFreq: this.framerate});

    this.interval = setInterval(()=>{
      if(this.inPlay)
        this.gameStep();
    },this.framerate);
  }

  gameStep(){
    let now = Date.now();

    this.leftController.intervalFunc(now);
    this.rightController.intervalFunc(now);

    this.leftPaddle.direction = this.leftController.direction;
    this.rightPaddle.direction = this.rightController.direction;

    this.ball.timeStep();
    this.leftPaddle.timeStep();
    this.rightPaddle.timeStep();

    //Handle Wall Collisions
    if(this.ball.x < 0 + this.ball.rad){
      //right player loss
      this.ball.x = this.canvas.width/2;
    }

    if(this.ball.x > this.canvas.width - this.ball.rad){
      //left player loss
      this.ball.x = this.canvas.width/2;
    }

    if(this.ball.y < 0 + this.ball.rad){
      //bounce
    }

    if(this.ball.y > this.canvas.height - this.ball.rad){
      //bounce
    }

    //Handle Paddle Collisions
    if((this.ball.x < this.leftPaddle.x + this.leftPaddle.width + this.ball.rad) &&
       (this.ball.y > this.leftPaddle.y && this.ball.y < this.leftPaddle.y + this.leftPaddle.length)){
      //bounce
      this.ball.vec.x = -this.ball.vec.x
    }

    if((this.ball.x > this.rightPaddle.x - this.ball.rad) &&
       (this.ball.y > this.rightPaddle.y && this.ball.y < this.rightPaddle.y + this.rightPaddle.length)){
      //bounce
      this.ball.vec.x = -this.ball.vec.x
    }

    this.canvas.clear();
    this.ball.drawable(this.canvas.ctx);
    this.leftPaddle.drawable(this.canvas.ctx);
    this.rightPaddle.drawable(this.canvas.ctx);

  }
}

function vectorToString(vector, digits) {
  if (typeof digits === "undefined") {
    digits = 1;
  }
  return "(" + vector[0].toFixed(digits) + ", "
             + vector[1].toFixed(digits) + ", "
             + vector[2].toFixed(digits) + ")";
}

(()=>{
   new Pong(document.getElementById("canvas"));
})();<commit_msg>Working Pong added dumb AI and dumb Angle Calculation needs improvement<commit_after>'use strict'
class Ball {
  constructor(args){
    this.x     = args.x;
    this.y     = args.y;
    this.rad   = args.rad;
    this.vec   = args.vec;
    this.color = "rgba(0,0,0,1)";
  }

  timeStep(){
    this.x = this.x+this.vec.x;
    this.y = this.y+this.vec.y;
  }

  drawable(ctx){
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.rad, 0, 2 * Math.PI, false);
    ctx.fillStyle = this.color;
    ctx.fill();
    ctx.lineWidth = 0;
    ctx.strokeWidth = 0;
    ctx.strokeStyle = 'rgba(0,0,0,0)';
    ctx.stroke();
  }
}

class Paddle {
  constructor(args){
    this.length    = 100;
    this.width     = 5;
    this.x         = args.x;
    this.y         = args.y;
    this.totalH    = args.totalH;
    this.direction = 0;
    this.speed     = args.speed;
    this.color     = "rgba(0,0,0,1)";
  }

  timeStep(){
    if(this.y > 0 && this.direction < 0)
      this.y += this.direction*this.speed;
    else if(this.y < this.totalH-this.length && this.direction > 0)
      this.y += this.direction*this.speed;
  }

  drawable(ctx){
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x,this.y,this.width,this.length);
  }
}

class Canvas {
  constructor(args){
    this.canvasElem = args.canvasElem;
    this.ctx = args.canvasElem.getContext('2d');
    this.rect = this.canvasElem.getBoundingClientRect();
    this.width = this.rect.width;
    this.height = this.rect.height;
    this.canvasElem.width = this.width;
    this.canvasElem.height = this.height;
    this.backgroundColor = args.backgroundColor;
    this.forgroundColor = args.forgroundColor;
  }

  clear(){
    this.ctx.beginPath();
    this.ctx.fillStyle = this.backgroundColor;
    this.ctx.fillRect(0,0,this.width,this.height);
    this.ctx.strokeStyle = this.forgroundColor;
    this.ctx.strokeWidth = 2;
    this.ctx.rect(0,0,this.width,this.height);
    this.ctx.stroke();
    this.ctx.beginPath();
    this.ctx.moveTo(this.width/2,0);
    this.ctx.lineTo(this.width/2,this.height);
    this.ctx.stroke();
  }
}

class Controller{
  constructor(args){
    this.sampleFreq = args.sampleFreq;
    this.timestamp = Date.now();
    this.direction = 0;
    this.up = false;
    this.down = false;

    this.intervalFunc = (now)=>{
      if((now - this.timestamp) > this.sampleFreq){
        this.direction = 0;
      }
    };
  }
}

class KeyboardController extends Controller{
  constructor(args){
    super(args);
    console.log(this.direction);
    document.onkeypress = (e)=>{
      e = e || window.event;
      var charCode = (typeof e.which == "number") ? e.which : e.keyCode;
      if (charCode) {
        if(charCode == 119){ //'w'
          this.direction = -1;
          this.timestamp = Date.now();
        }
        if(charCode == 115){ //'s'
          this.direction = 1;
          this.timestamp = Date.now();
        }
      }
    };
  }
}

class LeapController extends Controller{
  constructor(args){
    super(args);

    const controllerOptions = {enableGestures: true};
    let setDirection = (dir) => {
                         this.direction = dir
                         this.timestamp = Date.now();
                        };

    //console.log("Starting Loop");
    Leap.loop(controllerOptions, function(frame) {
      //var interactionBox = frame.interactionBox;

      if (frame.hands.length > 0) {
        for (let i = 0; i < frame.hands.length; i++) {
          let hand = frame.hands[i];
          if(hand.indexFinger.extended){
            let nameMap = ["thumb", "index", "middle", "ring", "pinky"];
            let pointer = null;
            hand.pointables.forEach(function(pointable){
              if(nameMap[pointable.type] == "index") pointer = pointable;
            });
            if(pointer){
              let direction = pointer.direction;
              if(direction[1] > 0.3){
                setDirection(-1);
              }
              if(direction[1] < -0.3){
                setDirection(1);
              }
            }
          }
        }
      }
    });
  }

}

class AIController extends Controller{
  constructor(args){
    super(args);
    this.ball   = args.ball;
    this.paddle = args.paddle;

    this.interval = setInterval(()=>{
      let top = this.paddle.y + this.paddle.length/2 - 25;
      let bottom = this.paddle.y + this.paddle.length/2 + 25;

      if(top < this.ball.y){
        //console.log("moving down");
        this.direction = 1;
        this.timestamp = Date.now();
      }
      if(bottom > this.ball.y){
        //console.log("moving up");
        this.direction = -1;
        this.timestamp = Date.now();
      }

      if(top > this.ball.y && bottom < this.ball.y){
        this.direction = 0;
        this.timestamp = Date.now();
      }
    },this.sampleFreq);
  }
}

class Pong {
  constructor(canvas){
    this.framerate       = 20;
    this.inPlay          = true;
    this.canvas          = new Canvas({canvasElem:canvas,
                                       backgroundColor: "rgba(255,255,255,1)",
                                       forgroundColor: "rgba(0,0,0,1)"
                           });

    this.ball            = new Ball({x:this.canvas.width/2,
                                      y:this.canvas.height/2,
                                      rad:5,
                                      vec:{x:5,y:0},
                                      color:"rgba(0,0,0,1)"
                           });

    this.leftPaddle      = new Paddle({x:10,
                                       y:(canvas.height/2)-50,
                                       speed: 4,
                                       totalH: this.canvas.height
                           });

    this.rightPaddle     = new Paddle({x:canvas.width-10,
                                       y:(canvas.height/2)-50,
                                       speed: 4,
                                       totalH: this.canvas.height
                           });

    this.leftController  = new AIController({sampleFreq: this.framerate,
                                             ball: this.ball,
                                             paddle: this.leftPaddle
                           });

    this.rightController = new LeapController({sampleFreq: this.framerate});

    this.interval = setInterval(()=>{
      if(this.inPlay)
        this.gameStep();
    },this.framerate);
  }

  gameStep(){
    let now = Date.now();

    this.leftController.intervalFunc(now);
    this.rightController.intervalFunc(now);

    this.leftPaddle.direction = this.leftController.direction;
    this.rightPaddle.direction = this.rightController.direction;

    this.ball.timeStep();
    this.leftPaddle.timeStep();
    this.rightPaddle.timeStep();

    //Handle Wall Collisions
    if(this.ball.x < 0 + this.ball.rad){
      //right player loss
      this.ball.x = this.canvas.width/2;
      this.ball.y = this.canvas.height/2;
      this.ball.vec = {x:5,y:0}
    }

    if(this.ball.x > this.canvas.width - this.ball.rad){
      //left player loss
      this.ball.x = this.canvas.width/2;
      this.ball.y = this.canvas.height/2;
      this.ball.vec = {x:-5,y:0}
    }

    if(this.ball.y < 0 + this.ball.rad){
      //bounce mirror
      this.ball.vec.y = -this.ball.vec.y;
    }

    if(this.ball.y > this.canvas.height - this.ball.rad){
      //bounce mirror
      this.ball.vec.y = -this.ball.vec.y;
    }

    //Handle Paddle Collisions
    if((this.ball.x < this.leftPaddle.x + this.leftPaddle.width + this.ball.rad) &&
       (this.ball.y > this.leftPaddle.y && this.ball.y < this.leftPaddle.y + this.leftPaddle.length)){
      //bounce
      this.ball.vec.x = -this.ball.vec.x
      let yreflectmod = this.ball.y - (this.leftPaddle.y + this.leftPaddle.length/2);
      if(yreflectmod > 2){ //Dumb angle calulation
        yreflectmod = 2;
      }
      if(yreflectmod < -2){
        yreflectmod = -2;
      }
      this.ball.vec.y = this.ball.vec.y + yreflectmod;
    }

    if((this.ball.x > this.rightPaddle.x - this.ball.rad) &&
       (this.ball.y > this.rightPaddle.y && this.ball.y < this.rightPaddle.y + this.rightPaddle.length)){
      //bounce
      this.ball.vec.x = -this.ball.vec.x
      let yreflectmod = this.ball.y - (this.rightPaddle.y + this.rightPaddle.length/2);
      if(yreflectmod > 2){ //Dumb angle calculation
        yreflectmod = 2;
      }
      if(yreflectmod < -2){
        yreflectmod = -2;
      }
      this.ball.vec.y = this.ball.vec.y + yreflectmod;
    }

    this.canvas.clear();
    this.ball.drawable(this.canvas.ctx);
    this.leftPaddle.drawable(this.canvas.ctx);
    this.rightPaddle.drawable(this.canvas.ctx);

  }
}

function vectorToString(vector, digits) {
  if (typeof digits === "undefined") {
    digits = 1;
  }
  return "(" + vector[0].toFixed(digits) + ", "
             + vector[1].toFixed(digits) + ", "
             + vector[2].toFixed(digits) + ")";
}

(()=>{
   new Pong(document.getElementById("canvas"));
})();<|endoftext|>


<commit_before>import { rgba } from 'polished'
import styled from '../styled'
import Animate from '../Animate'
import Text from '../Text'
import baseStyles from '../../styles/resets/baseStyles.css.js'
import { getColor } from '../../styles/utilities/color'

export const config = {
  borderRadius: {
    md: '8px',
    sm: '2px',
  },
  bubbleClassName: '.c-Notification__messageBubble',
}

export const NotificationUI = styled(Animate)`
  ${baseStyles};
  max-width: 300px;

  ${config.bubbleClassName} {
    background-color: white;
    border-color: transparent !important;
    box-shadow: 0 0 0 1px ${rgba('black', 0.1)},
      0 4px 6px ${rgba('black', 0.15)};
    cursor: pointer;
    max-width: 100%;
    text-align: right;
    transition: border-radius 200ms ease;
  }

  & + & {
    ${config.bubbleClassName} {
      border-top-right-radius: ${config.borderRadius.md} !important;
    }
  }

  &:last-child {
    ${config.bubbleClassName} {
      border-top-right-radius: ${config.borderRadius.md} !important;
      border-bottom-left-radius: ${config.borderRadius.md} !important;
    }
  }

  &.is-align-right {
    ${config.bubbleClassName} {
      border-top-left-radius: ${config.borderRadius.md} !important;
      border-top-right-radius: ${config.borderRadius.sm} !important;
      border-bottom-right-radius: ${config.borderRadius.sm} !important;
      border-bottom-left-radius: ${config.borderRadius.md} !important;
    }

    &:only-child {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.md} !important;
      }
    }

    &:not(:only-child) {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.sm} !important;
      }
    }

    &:first-child:not(:last-child) {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.md} !important;
      }
    }
  }

  &.is-align-left {
    ${config.bubbleClassName} {
      border-top-left-radius: ${config.borderRadius.sm} !important;
      border-top-right-radius: ${config.borderRadius.md} !important;
      border-bottom-right-radius: ${config.borderRadius.md} !important;
      border-bottom-left-radius: ${config.borderRadius.sm} !important;
      text-align: left;
    }

    .c-MessageChatBlock__flexy {
      justify-content: flex-start;
    }
    .c-MessageBubble__from {
      text-align: left;
    }

    &:only-child {
      ${config.bubbleClassName} {
        border-top-left-radius: ${config.borderRadius.md} !important;
      }
    }

    &:not(:only-child) {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.md} !important;
        border-bottom-left-radius: ${config.borderRadius.sm} !important;
      }
    }

    &:first-child:not(:only-child) {
      ${config.bubbleClassName} {
        border-top-left-radius: ${config.borderRadius.md} !important;
        border-bottom-left-radius: ${config.borderRadius.sm} !important;
        border-bottom-right-radius: ${config.borderRadius.md} !important;
      }
    }
  }
`

export const TextUI = styled(Text)`
  color: ${getColor('charcoal.500')};
`

export const TimerUI = styled('div')`
  ${baseStyles};
  animation-fill-mode: forward;
  animation-name: HSDSNotificationTimer;
  animation-iteration-count: 1;
  animation-duration: 5000ms;
  animation-play-state: running;

  @keyframes HSDSNotificationTimer {
  }
`
<commit_msg>Fix alignment of Notifications<commit_after>import { rgba } from 'polished'
import styled from '../styled'
import Animate from '../Animate'
import Text from '../Text'
import baseStyles from '../../styles/resets/baseStyles.css.js'
import { getColor } from '../../styles/utilities/color'

export const config = {
  borderRadius: {
    md: '8px',
    sm: '2px',
  },
  bubbleClassName: '.c-Notification__messageBubble',
}

export const NotificationUI = styled(Animate)`
  ${baseStyles};
  max-width: 300px;

  ${config.bubbleClassName} {
    background-color: white;
    border-color: transparent !important;
    box-shadow: 0 0 0 1px ${rgba('black', 0.1)},
      0 4px 6px ${rgba('black', 0.15)};
    cursor: pointer;
    max-width: 100%;
    text-align: right;
    transition: border-radius 200ms ease;
  }

  & + & {
    ${config.bubbleClassName} {
      border-top-right-radius: ${config.borderRadius.md} !important;
    }
  }

  &:last-child {
    ${config.bubbleClassName} {
      border-top-right-radius: ${config.borderRadius.md} !important;
      border-bottom-left-radius: ${config.borderRadius.md} !important;
    }
  }

  &.is-align-right {
    margin-left: auto;

    ${config.bubbleClassName} {
      border-top-left-radius: ${config.borderRadius.md} !important;
      border-top-right-radius: ${config.borderRadius.sm} !important;
      border-bottom-right-radius: ${config.borderRadius.sm} !important;
      border-bottom-left-radius: ${config.borderRadius.md} !important;
    }

    &:only-child {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.md} !important;
      }
    }

    &:not(:only-child) {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.sm} !important;
      }
    }

    &:first-child:not(:last-child) {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.md} !important;
      }
    }
  }

  &.is-align-left {
    margin-right: auto;

    ${config.bubbleClassName} {
      border-top-left-radius: ${config.borderRadius.sm} !important;
      border-top-right-radius: ${config.borderRadius.md} !important;
      border-bottom-right-radius: ${config.borderRadius.md} !important;
      border-bottom-left-radius: ${config.borderRadius.sm} !important;
      text-align: left;
    }

    .c-MessageChatBlock__flexy {
      justify-content: flex-start;
    }
    .c-MessageBubble__from {
      text-align: left;
    }

    &:only-child {
      ${config.bubbleClassName} {
        border-top-left-radius: ${config.borderRadius.md} !important;
      }
    }

    &:not(:only-child) {
      ${config.bubbleClassName} {
        border-top-right-radius: ${config.borderRadius.md} !important;
        border-bottom-left-radius: ${config.borderRadius.sm} !important;
      }
    }

    &:first-child:not(:only-child) {
      ${config.bubbleClassName} {
        border-top-left-radius: ${config.borderRadius.md} !important;
        border-bottom-left-radius: ${config.borderRadius.sm} !important;
        border-bottom-right-radius: ${config.borderRadius.md} !important;
      }
    }
  }
`

export const TextUI = styled(Text)`
  color: ${getColor('charcoal.500')};
`

export const TimerUI = styled('div')`
  ${baseStyles};
  animation-fill-mode: forward;
  animation-name: HSDSNotificationTimer;
  animation-iteration-count: 1;
  animation-duration: 5000ms;
  animation-play-state: running;

  @keyframes HSDSNotificationTimer {
  }
`
<|endoftext|>


<commit_before>var cors_http = require("corsproxy");
var static_http = require("connect/lib/middleware/static");

module.exports = make_hoodie_server;

function make_hoodie_server(couchdb, name, host) {
  this.couchdb = "http://couch." + name + "." + host + ":" + couch_port,
  this.name = name;
  this.host = host;
  return hoodie_server.bind(this);
}

make_hoodie_server.prototype.serve_static = function(host, name) {
  if((host == "www." + name + "." + this.host)
    || (host == name + "." + this.host)) {
    return true;
  } else {
    return false;
  }
};

make_hoodie_server.prototype.serve_cors = function(host, name) {
  if(host == "api." + name + "." + this.host) {
    return true;
  } else {
    return false;
  }
};

make_hoodie_server.prototype.serve_admin = function(host, name) {
  if(host == "admin." + name + "." + this.host) {
    return true;
  } else {
    return false;
  }
};

var hoodie_server = function(req, res, proxy) {

  var host = req.headers.host;

  // frontend proxy duties
  //   if host == [www.]APPNAME.domain
  //     serve ./www
  if(this.serve_static(host, this.name)) {
    console.log("serve static req");
    var static_server = static_http("./www");
    return static_server(req, res, function() {});
  }

  //   if host == api.APPNAME.domain
  //     serve CORS
  if(this.serve_cors(host, this.name)) {
    console.log("serve api req");
    cors_http.options = {
      target: this.couchdb
    }
    return cors_http(req, res, proxy);
  }

  // launch httpd for Admin UI
  //   if host == admin.APPNAME.domain
  //     serve ./node-modules/hoodie-app/www
  if(this.serve_admin(host, this.name)) {
    console.log("serve admin req");
    var static_server = static_http("./node_modules/hoodie-app/www");
    return static_server(req, res, function() {});
  }

  // TBD add default handler
};
<commit_msg>refactor leftover 2<commit_after>var cors_http = require("corsproxy");
var static_http = require("connect/lib/middleware/static");

module.exports = make_hoodie_server;

function make_hoodie_server(name, host, couch_port) {
  this.couchdb = "http://couch." + name + "." + host + ":" + couch_port,
  this.name = name;
  this.host = host;
  return hoodie_server.bind(this);
}

make_hoodie_server.prototype.serve_static = function(host, name) {
  if((host == "www." + name + "." + this.host)
    || (host == name + "." + this.host)) {
    return true;
  } else {
    return false;
  }
};

make_hoodie_server.prototype.serve_cors = function(host, name) {
  if(host == "api." + name + "." + this.host) {
    return true;
  } else {
    return false;
  }
};

make_hoodie_server.prototype.serve_admin = function(host, name) {
  if(host == "admin." + name + "." + this.host) {
    return true;
  } else {
    return false;
  }
};

var hoodie_server = function(req, res, proxy) {

  var host = req.headers.host;

  // frontend proxy duties
  //   if host == [www.]APPNAME.domain
  //     serve ./www
  if(this.serve_static(host, this.name)) {
    console.log("serve static req");
    var static_server = static_http("./www");
    return static_server(req, res, function() {});
  }

  //   if host == api.APPNAME.domain
  //     serve CORS
  if(this.serve_cors(host, this.name)) {
    console.log("serve api req");
    cors_http.options = {
      target: this.couchdb
    }
    return cors_http(req, res, proxy);
  }

  // launch httpd for Admin UI
  //   if host == admin.APPNAME.domain
  //     serve ./node-modules/hoodie-app/www
  if(this.serve_admin(host, this.name)) {
    console.log("serve admin req");
    var static_server = static_http("./node_modules/hoodie-app/www");
    return static_server(req, res, function() {});
  }

  // TBD add default handler
};
<|endoftext|>


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

/**
 * Wrapper type for scenery's Focus region.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

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

  // phet-io modules
  var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' );
  var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' );
  var TObject = require( 'ifphetio!PHET_IO/types/TObject' );

  /**
   * Wrapper type for phet/sun's Faucet class.
   * @param {Object} focus - the focus region which has {display,trail}
   * @param {string} phetioID - the unique tandem assigned to the focus
   * @constructor
   */
  function TFocus( focus, phetioID ) {
    TObject.call( this, focus, phetioID );
    assertInstanceOf( focus, phet.scenery.Focus );
  }

  phetioInherit( TObject, 'TFocus', TFocus, {}, {

    /**
     * Convert the focus region to a plain JS object for serialization.
     * @param {Object} focus - the focus region which has {display,trail}
     * @returns {Object} - the serialized object
     */
    toStateObject: function( focus ) {

      // If nothing is focused, the focus is nulls
      if ( focus === null ) {
        return null;
      }
      else {
        return focus.trail.indices;
      }
    },

    /**
     * Convert the serialized instance back to a focus object
     * @param {number[]} indices
     * @returns {Object} with {display,trail}
     */
    fromStateObject: function( indices ) {

      if ( indices === null ) {

        // support unfocused
        return null;
      }
      else {

        // Follow the path of children based on their indices, starting from the root of the display.
        // There is always one more node in Trail than indices, representing the root node.
        var currentNode = phet.joist.sim.display.rootNode;
        var nodes = [ currentNode ];
        for ( var i = 0; i < indices.length; i++ ) {
          var index = indices[ i ];
          currentNode = currentNode.children[ index ];
          nodes.push( currentNode );
        }

        return { display: phet.joist.sim.display, trail: new phet.scenery.Trail( nodes ) };
      }
    },

    documentation: 'A wrapper type for the instance in the simulation which currently has keyboard focus.'
  } );

  scenery.register( 'TFocus', TFocus );

  return TFocus;
} );<commit_msg>Check assertion first, see https://github.com/phetsims/joist/issues/434<commit_after>// Copyright 2016, University of Colorado Boulder

/**
 * Wrapper type for scenery's Focus region.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Jesse Greenberg (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

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

  // phet-io modules
  var assertInstanceOf = require( 'ifphetio!PHET_IO/assertions/assertInstanceOf' );
  var phetioInherit = require( 'ifphetio!PHET_IO/phetioInherit' );
  var TObject = require( 'ifphetio!PHET_IO/types/TObject' );

  /**
   * Wrapper type for phet/sun's Faucet class.
   * @param {Object} focus - the focus region which has {display,trail}
   * @param {string} phetioID - the unique tandem assigned to the focus
   * @constructor
   */
  function TFocus( focus, phetioID ) {
    assertInstanceOf( focus, phet.scenery.Focus );
    TObject.call( this, focus, phetioID );
  }

  phetioInherit( TObject, 'TFocus', TFocus, {}, {

    /**
     * Convert the focus region to a plain JS object for serialization.
     * @param {Object} focus - the focus region which has {display,trail}
     * @returns {Object} - the serialized object
     */
    toStateObject: function( focus ) {

      // If nothing is focused, the focus is nulls
      if ( focus === null ) {
        return null;
      }
      else {
        return focus.trail.indices;
      }
    },

    /**
     * Convert the serialized instance back to a focus object
     * @param {number[]} indices
     * @returns {Object} with {display,trail}
     */
    fromStateObject: function( indices ) {

      if ( indices === null ) {

        // support unfocused
        return null;
      }
      else {

        // Follow the path of children based on their indices, starting from the root of the display.
        // There is always one more node in Trail than indices, representing the root node.
        var currentNode = phet.joist.sim.display.rootNode;
        var nodes = [ currentNode ];
        for ( var i = 0; i < indices.length; i++ ) {
          var index = indices[ i ];
          currentNode = currentNode.children[ index ];
          nodes.push( currentNode );
        }

        return { display: phet.joist.sim.display, trail: new phet.scenery.Trail( nodes ) };
      }
    },

    documentation: 'A wrapper type for the instance in the simulation which currently has keyboard focus.'
  } );

  scenery.register( 'TFocus', TFocus );

  return TFocus;
} );<|endoftext|>


<commit_before>var Utils     = require("./../utils")
  , HasOne    = require('./has-one')
  , HasMany   = require("./has-many")
  , BelongsTo = require("./belongs-to")

/**
 * Creating assocations in sequelize is done by calling one of the belongsTo / hasOne / hasMany functions
 * on a model (the source), and prodiving another model as the first argument to the function (the target).
 *
 * * hasOne - adds a foreign key to target
 * * belongsTo - add a foreign key to source
 * * hasMany - adds a foreign key to target, unless you also specifiy that target hasMany source, in which case a junction table is created with sourceId and targetId
 *
 * Creating an association will add a foreign key constraint to the attributes. All associations use `CASCADE` on update and `SET NULL` on delete, except for n:m, which also uses `CASCADE` on delete.
 *
 * When creating associations, you can provide an alias, via the `as` option. This is usefull if the same model
 * is associated twice, or you want your association to be called something other than the name of the target model.
 * As an example, consider the case where users have many pictures, one of which is their profile picture. All pictures
 * have a `userId`, but in addition the user model also has a `profilePictureId`, to be able to easily load the user's profile
 * picture.
 *
 * ```js
 * User.hasMany(Picture)
 * User.belongsTo(Picture, { as: 'ProfilePicture', constraints: false })
 *
 * user.getPictures() // gets you all pictures
 * user.getProfilePicture() // gets you only the profile picture
 *
 * User.findAll({
 *   where: ...,
 *   include: [
 *     { model: Picture }, // load all pictures
 *     { model: Picture, as: 'ProfilePicture' }, // load the profile picture. Notice that the spelling must be the exact same as the one in the association
 *   ]
 * })
 * ```
 *
 * When getting associated models, you can limit your query to only load some models. These queries are written in the same way as queries to `find`/`findAll`. To only get pictures in JPG, you can do:
 *
 * ```js
 * user.getPictures({
 *   where: {
 *     format: 'jpg'
 *   }
 * })
 * ```
 *
 * In the above we specify that a user belongs to his profile picture. Conceptually, this might not make sense,
 * but since we want to add the foreign key to the user model this is the way to do it.
 * Note how we also specified `constraints: false` for profile picture. This is because we add a foreign key from
 * user to picture (profilePictureId), and from picture to user (userId). If we were to add foreign keys to both, it would
 * create a cyclic dependency, and sequelize would not know which table to create first, since user depends on picture, and picture
 * depends on user. These kinds of problems are detected by sequelize before the models are synced to the database, and you will
 * get an error along the lines of `Error: Cyclic dependency found. 'users' is dependent of itself`. If you encounter this,
 * you should either disable some constraints, or rethink your associations completely.
 *
 * @mixin Associations
 */
var Mixin = module.exports = function(){}

/**
 * Creates an association between this (the source) and the provided target. The foreign key is added on the target.
 *
 * Example: `User.hasOne(Profile)`. This will add userId to the profile table.
 *
 * The following methods are injected on the source:
 *
 * * get[AS] - for example getProfile()
 * * set[AS] - for example setProfile(instance, options). Options are passed to `target.save`
 * * create[AS] - for example createProfile(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to `target.create`
 *
 * All methods return an event emitter.
 *
 * @param {Model} target
 * @param {object}     [options]
 * @param {boolean}           [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
 * @param {string}     [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
 * @param {string}     [options.foreignKey] The name of the foreign key in the target table. Defaults to the name of source + primary key of source
 * @param {string}     [options.onDelete='SET NULL']
 * @param {string}     [options.onUpdate='CASCADE']
 * @param {boolean}    [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
 */
Mixin.hasOne = function(targetModel, options) {
  var sourceModel = this

  // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
  options = options || {}
  options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
  options.useHooks = options.hooks

  // the id is in the foreign table
  var association = new HasOne(sourceModel, targetModel, Utils._.extend(options, sourceModel.options))
  sourceModel.associations[association.associationAccessor] = association.injectAttributes()

  association.injectGetter(sourceModel.Instance.prototype);
  association.injectSetter(sourceModel.Instance.prototype);
  association.injectCreator(sourceModel.Instance.prototype);

  return association
}

/**
 * Creates an association between this (the source) and the provided target. The foreign key is added on the source.
 *
 * Example: `Profile.belongsTo(User)`. This will add userId to the profile table.
 *
 * The following methods are injected on the source:
 *
 * * get[AS] - for example getUser()
 * * set[AS] - for example setUser(instance, options). Options are passed to this.save
 * * create[AS] - for example createUser(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to target.create
 *
 * All methods return an event emitter.
 *
 * @param {Model} target
 * @param {object}     [options]
 * @param {boolean}           [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
 * @param {string}     [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
 * @param {string}     [options.foreignKey] The name of the foreign key in the source table. Defaults to the name of target + primary key of target
 * @param {string}     [options.onDelete='SET NULL']
 * @param {string}     [options.onUpdate='CASCADE']
 * @param {boolean}    [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
 */
Mixin.belongsTo = function(targetModel, options) {
  var sourceModel = this

  // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
  options = options || {}
  options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
  options.useHooks = options.hooks

  // the id is in this table
  var association = new BelongsTo(sourceModel, targetModel, Utils._.extend(options, sourceModel.options))
  sourceModel.associations[association.associationAccessor] = association.injectAttributes()

  association.injectGetter(sourceModel.Instance.prototype)
  association.injectSetter(sourceModel.Instance.prototype)
  association.injectCreator(sourceModel.Instance.prototype)

  return association
}

/**
 * Create an association that is either 1:m or n:m.
 *
 * ```js
 * // Create a 1:m association between user and project
 * User.hasMany(Project)
 * ```
 * ```js
 * // Create a n:m association between user and project
 * User.hasMany(Project)
 * Project.hasMany(User)
 * ```
 * By default, the name of the join table will be source+target, so in this case projectsusers. This can be overridden by providing either a string or a Model as `through` in the options.

 * The following methods are injected on the source:
 *
 * * get[AS] - for example getPictures().
 * * set[AS] - for example setPictures(instances, defaultAttributes|options). Update the associations. All currently associated models that are not in instances will be removed.
 * * add[AS] - for example addPicture(instance, defaultAttributes|options). Add another association.
 * * create[AS] - for example createPicture(values, options). Build and save a new association.
 * * remove[AS] - for example removePicture(instance). Remove a single association
 * * has[AS] - for example hasPicture(instance). Is source associated to this target?
 * * has[AS] [plural] - for example hasPictures(instances). Is source associated to all these targets?
 *
 * All methods return an event emitter.
 *
 * If you use a through model with custom attributes, these attributes can be set when adding / setting new associations in two ways. Consider users and projects from before
 * with a join table that stores whether the project has been started yet:
 * ```js
 * var UserProjects = sequelize.define('userprojects', {
 *   started: Sequelize.BOOLEAN
 * })
 * User.hasMany(Project, { through: UserProjects })
 * Project.hasMany(User, { through: UserProjects })
 * ```
 * ```js
 * jan.addProject(homework, { started: false }) // The homework project is not started yet
 * jan.setProjects([makedinner, doshopping], { started: true}) // Both shopping and dinner has been started
 * ```
 *
 * If you want to set several target instances, but with different attributes you have to set the attributes on the instance, using a property with the name of the through model:
 *
 * ```js
 * p1.userprojects {
 *   started: true
 * }
 * user.setProjects([p1, p2], {started: false}) // The default value is false, but p1 overrides that.
 * ```
 *
 * Similarily, when fetching through a join table with custom attributes, these attributes will be available as an object with the name of the through model.
 * ```js
 * user.getProjects().success(function (projects) {
 *   var p1 = projects[0]
 *   p1.userprojects.started // Is this project started yet?
 * })
 * ```
 *
 * @param {Model}        target
 * @param {object}            [options]
 * @param {boolean}           [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
 * @param {Model|string} [options.through] The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it.
 * @param {string}            [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
 * @param {string}            [options.foreignKey] The name of the foreign key in the source table. Defaults to the name of target + primary key of target
 * @param {string}            [options.onDelete='SET NULL|CASCADE'] Cascade if this is a n:m, and set null if it is a 1:m
 * @param {string}            [options.onUpdate='CASCADE']
 * @param {boolean}           [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
 */
Mixin.hasMany = function(targetModel, options) {
  var sourceModel = this

  // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
  options = options || {}
  options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
  options.useHooks = options.hooks

  options = Utils._.extend(options, Utils._.omit(sourceModel.options, ['hooks']))

  // the id is in the foreign table or in a connecting table
  var association = new HasMany(sourceModel, targetModel, options)
  sourceModel.associations[association.associationAccessor] = association.injectAttributes()

  association.injectGetter(sourceModel.Instance.prototype)
  association.injectSetter(sourceModel.Instance.prototype)
  association.injectCreator(sourceModel.Instance.prototype)

  return association
}

Mixin.getAssociation = function(target, alias) {
  for (var associationName in this.associations) {
    if (this.associations.hasOwnProperty(associationName)) {
      var association = this.associations[associationName]

      if (association.target === target && (alias === undefined ? !association.isAliased : association.as === alias)) {
        return association
      }
    }
  }

  return null
}

Mixin.getAssociationByAlias = function(alias) {
  for (var associationName in this.associations) {
    if (this.associations.hasOwnProperty(associationName)) {
      var association = this.associations[associationName]

      if (association.as === alias) {
        return association
      }
    }
  }

  return null
}<commit_msg>Put   back in<commit_after>var Utils     = require("./../utils")
  , HasOne    = require('./has-one')
  , HasMany   = require("./has-many")
  , BelongsTo = require("./belongs-to")

/**
 * Creating assocations in sequelize is done by calling one of the belongsTo / hasOne / hasMany functions
 * on a model (the source), and prodiving another model as the first argument to the function (the target).
 *
 * * hasOne - adds a foreign key to target
 * * belongsTo - add a foreign key to source
 * * hasMany - adds a foreign key to target, unless you also specifiy that target hasMany source, in which case a junction table is created with sourceId and targetId
 *
 * Creating an association will add a foreign key constraint to the attributes. All associations use `CASCADE` on update and `SET NULL` on delete, except for n:m, which also uses `CASCADE` on delete.
 *
 * When creating associations, you can provide an alias, via the `as` option. This is usefull if the same model
 * is associated twice, or you want your association to be called something other than the name of the target model.
 * As an example, consider the case where users have many pictures, one of which is their profile picture. All pictures
 * have a `userId`, but in addition the user model also has a `profilePictureId`, to be able to easily load the user's profile
 * picture.
 *
 * ```js
 * User.hasMany(Picture)
 * User.belongsTo(Picture, { as: 'ProfilePicture', constraints: false })
 *
 * user.getPictures() // gets you all pictures
 * user.getProfilePicture() // gets you only the profile picture
 *
 * User.findAll({
 *   where: ...,
 *   include: [
 *     { model: Picture }, // load all pictures
 *     { model: Picture, as: 'ProfilePicture' }, // load the profile picture. Notice that the spelling must be the exact same as the one in the association
 *   ]
 * })
 * ```
 *
 * When getting associated models, you can limit your query to only load some models. These queries are written in the same way as queries to `find`/`findAll`. To only get pictures in JPG, you can do:
 *
 * ```js
 * user.getPictures({
 *   where: {
 *     format: 'jpg'
 *   }
 * })
 * ```
 *
 * In the above we specify that a user belongs to his profile picture. Conceptually, this might not make sense,
 * but since we want to add the foreign key to the user model this is the way to do it.
 * Note how we also specified `constraints: false` for profile picture. This is because we add a foreign key from
 * user to picture (profilePictureId), and from picture to user (userId). If we were to add foreign keys to both, it would
 * create a cyclic dependency, and sequelize would not know which table to create first, since user depends on picture, and picture
 * depends on user. These kinds of problems are detected by sequelize before the models are synced to the database, and you will
 * get an error along the lines of `Error: Cyclic dependency found. 'users' is dependent of itself`. If you encounter this,
 * you should either disable some constraints, or rethink your associations completely.
 *
 * @mixin Associations
 */
var Mixin = module.exports = function(){}

/**
 * Creates an association between this (the source) and the provided target. The foreign key is added on the target.
 *
 * Example: `User.hasOne(Profile)`. This will add userId to the profile table.
 *
 * The following methods are injected on the source:
 *
 * * get[AS] - for example getProfile()
 * * set[AS] - for example setProfile(instance, options). Options are passed to `target.save`
 * * create[AS] - for example createProfile(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to `target.create`
 *
 * All methods return an event emitter.
 *
 * @param {Model} target
 * @param {object}     [options]
 * @param {boolean}           [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
 * @param {string}     [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
 * @param {string}     [options.foreignKey] The name of the foreign key in the target table. Defaults to the name of source + primary key of source
 * @param {string}     [options.onDelete='SET NULL']
 * @param {string}     [options.onUpdate='CASCADE']
 * @param {boolean}    [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
 */
Mixin.hasOne = function(targetModel, options) {
  var sourceModel = this

  // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
  options = options || {}
  options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
  options.useHooks = options.hooks

  // the id is in the foreign table
  var association = new HasOne(sourceModel, targetModel, Utils._.extend(options, sourceModel.options))
  sourceModel.associations[association.associationAccessor] = association.injectAttributes()

  association.injectGetter(sourceModel.Instance.prototype);
  association.injectSetter(sourceModel.Instance.prototype);
  association.injectCreator(sourceModel.Instance.prototype);

  return association
}

/**
 * Creates an association between this (the source) and the provided target. The foreign key is added on the source.
 *
 * Example: `Profile.belongsTo(User)`. This will add userId to the profile table.
 *
 * The following methods are injected on the source:
 *
 * * get[AS] - for example getUser()
 * * set[AS] - for example setUser(instance, options). Options are passed to this.save
 * * create[AS] - for example createUser(value, options). Builds and saves a new instance of the associated model. Values and options are passed on to target.create
 *
 * All methods return an event emitter.
 *
 * @param {Model} target
 * @param {object}     [options]
 * @param {boolean}           [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
 * @param {string}     [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
 * @param {string}     [options.foreignKey] The name of the foreign key in the source table. Defaults to the name of target + primary key of target
 * @param {string}     [options.onDelete='SET NULL']
 * @param {string}     [options.onUpdate='CASCADE']
 * @param {boolean}    [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
 */
Mixin.belongsTo = function(targetModel, options) {
  var sourceModel = this

  // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
  options = options || {}
  options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
  options.useHooks = options.hooks

  // the id is in this table
  var association = new BelongsTo(sourceModel, targetModel, Utils._.extend(options, sourceModel.options))
  sourceModel.associations[association.associationAccessor] = association.injectAttributes()

  association.injectGetter(sourceModel.Instance.prototype)
  association.injectSetter(sourceModel.Instance.prototype)
  association.injectCreator(sourceModel.Instance.prototype)

  return association
}

/**
 * Create an association that is either 1:m or n:m.
 *
 * ```js
 * // Create a 1:m association between user and project
 * User.hasMany(Project)
 * ```
 * ```js
 * // Create a n:m association between user and project
 * User.hasMany(Project)
 * Project.hasMany(User)
 * ```
 * By default, the name of the join table will be source+target, so in this case projectsusers. This can be overridden by providing either a string or a Model as `through` in the options.

 * The following methods are injected on the source:
 *
 * * get[AS] - for example getPictures().
 * * set[AS] - for example setPictures(instances, defaultAttributes|options). Update the associations. All currently associated models that are not in instances will be removed.
 * * add[AS] - for example addPicture(instance, defaultAttributes|options). Add another association.
 * * create[AS] - for example createPicture(values, options). Build and save a new association.
 * * remove[AS] - for example removePicture(instance). Remove a single association
 * * has[AS] - for example hasPicture(instance). Is source associated to this target?
 * * has[AS] [plural] - for example hasPictures(instances). Is source associated to all these targets?
 *
 * All methods return an event emitter.
 *
 * If you use a through model with custom attributes, these attributes can be set when adding / setting new associations in two ways. Consider users and projects from before
 * with a join table that stores whether the project has been started yet:
 * ```js
 * var UserProjects = sequelize.define('userprojects', {
 *   started: Sequelize.BOOLEAN
 * })
 * User.hasMany(Project, { through: UserProjects })
 * Project.hasMany(User, { through: UserProjects })
 * ```
 * ```js
 * jan.addProject(homework, { started: false }) // The homework project is not started yet
 * jan.setProjects([makedinner, doshopping], { started: true}) // Both shopping and dinner has been started
 * ```
 *
 * If you want to set several target instances, but with different attributes you have to set the attributes on the instance, using a property with the name of the through model:
 *
 * ```js
 * p1.userprojects {
 *   started: true
 * }
 * user.setProjects([p1, p2], {started: false}) // The default value is false, but p1 overrides that.
 * ```
 *
 * Similarily, when fetching through a join table with custom attributes, these attributes will be available as an object with the name of the through model.
 * ```js
 * user.getProjects().success(function (projects) {
 *   var p1 = projects[0]
 *   p1.userprojects.started // Is this project started yet?
 * })
 * ```
 *
 * @param {Model}        target
 * @param {object}            [options]
 * @param {boolean}           [options.hooks=false] Set to true to run before-/afterDestroy hooks when an associated model is deleted because of a cascade. For example if `User.hasOne(Profile, {onDelete: 'cascade', hooks:true})`, the before-/afterDestroy hooks for profile will be called when a user is deleted. Otherwise the profile will be deleted without invoking any hooks
 * @param {Model|string} [options.through] The name of the table that is used to join source and target in n:m associations. Can also be a sequelize model if you want to define the junction table yourself and add extra attributes to it.
 * @param {string}            [options.as] The alias of this model. If you create multiple associations between the same tables, you should provide an alias to be able to distinguish between them. If you provide an alias when creating the assocition, you should provide the same alias when eager loading and when getting assocated models. Defaults to the singularized version of target.name
 * @param {string}            [options.foreignKey] The name of the foreign key in the source table. Defaults to the name of target + primary key of target
 * @param {string}            [options.onDelete='SET NULL|CASCADE'] Cascade if this is a n:m, and set null if it is a 1:m
 * @param {string}            [options.onUpdate='CASCADE']
 * @param {boolean}           [options.constraints=true] Should on update and on delete constraints be enabled on the foreign key.
 */
Mixin.hasMany = function(targetModel, options) {
  var sourceModel = this

  // Since this is a mixin, we'll need a unique variable name for hooks (since Model will override our hooks option)
  options = options || {}
  options.hooks = options.hooks === undefined ? false : Boolean(options.hooks)
  options.useHooks = options.hooks

  options = Utils._.extend(options, Utils._.omit(sourceModel.options, ['hooks']))

  // the id is in the foreign table or in a connecting table
  var association = new HasMany(sourceModel, targetModel, options)
  sourceModel.associations[association.associationAccessor] = association.injectAttributes()

  association.injectGetter(sourceModel.Instance.prototype)
  association.injectSetter(sourceModel.Instance.prototype)
  association.injectCreator(sourceModel.Instance.prototype)

  return association
}

Mixin.getAssociation = function(target, alias) {
  for (var associationName in this.associations) {
    if (this.associations.hasOwnProperty(associationName)) {
      var association = this.associations[associationName]

      if (association.target === target && (alias === undefined ? !association.isAliased : association.as === alias)) {
        return association
      }
    }
  }

  return null
}

Mixin.getAssociationByAlias = function(alias) {
  for (var associationName in this.associations) {
    if (this.associations.hasOwnProperty(associationName)) {
      var association = this.associations[associationName]

      if (association.as === alias) {
        return association
      }
    }
  }

  return null
}<|endoftext|>


<commit_before>import angular from 'angular'

class Questions {
  constructor ($http) {
    this.http = $http
    this.o = {
      questions: []
    }
  }

  getAll () {
    return this.http.get('/api/questions').then((data) => {
      angular.copy(data, this.o.questions)
    })
  }

  create (question) {
    return this.http.post('/api/questions', question).then((data) => {
      this.o.questions.push(data)
    })
  }

  import (questions) {
    return this.http.post('/api/questions/import', questions).then((data) => {
      angular.extend(this.o.questions, data)
    })
  }

  // get: function(id) {
  //   /* this worked fine - with $scope.question = question; after the js resolve
  //   return this.http.get('/api/questions/' + id).then(function(res){
  //     return res.data;
  //   }); */
  //   return this.http.get('/api/questions/' + id).then(function(res){
  //     return res;
  //   });
  // },

  // addQuestion: function(questionID, question) {
  //   return this.http.post('/api/questions/' + questionID + '/questions', question);
  //   // FIXME: add question to o object here explicitly??
  // },

  editQuestion (id, question) {
    return this.http.put(`/api/questions/${id}`, question).then((data) => {
      this.o.questions.forEach((ea) => {
        if (ea._id === data.question) {
          ea.questions.forEach((q) => {
            if (q._id === data._id) {
              q = data
            }
          })
        }
      })
    })
  }

  // must delete question, and delete reference to it in question
  delete (question, questionID) {
    this.http.delete(`/api/questions/${question._id}`)
    console.log('successfully deleted q')
  }

  // editquestion: function(id, question) {
  //   question.questions.forEach(function(ea, i) {
  //     question.questions[i] = ea._id;
  //   });

  //   return this.http.put('/api/questions/' + id, question).then(function(data) {
  //     //var index = o.questions.indexOf(data._id);
  //     o.questions.forEach(function(ea) {
  //       if (ea._id === data._id) {
  //         angular.copy(data, ea);
  //       }
  //     });
  //   });
  // }
}

Questions.$inject = ['$http']

export default angular.module('bitfit.services.questions', [])
  .service('Questions', Questions)
  .name
<commit_msg>Display all questions<commit_after>import angular from 'angular'

class Questions {
  constructor ($http) {
    this.http = $http
    this.o = {
      questions: []
    }
  }

  getAll () {
    return this.http.get('/api/questions').then((data) => {
      angular.copy(data.data, this.o.questions)
      return this.o.questions
    })
  }

  create (question) {
    return this.http.post('/api/questions', question).then((data) => {
      this.o.questions.push(data)
    })
  }

  import (questions) {
    return this.http.post('/api/questions/import', questions).then((data) => {
      angular.extend(this.o.questions, data)
    })
  }

  // get: function(id) {
  //   /* this worked fine - with $scope.question = question; after the js resolve
  //   return this.http.get('/api/questions/' + id).then(function(res){
  //     return res.data;
  //   }); */
  //   return this.http.get('/api/questions/' + id).then(function(res){
  //     return res;
  //   });
  // },

  // addQuestion: function(questionID, question) {
  //   return this.http.post('/api/questions/' + questionID + '/questions', question);
  //   // FIXME: add question to o object here explicitly??
  // },

  editQuestion (id, question) {
    return this.http.put(`/api/questions/${id}`, question).then((data) => {
      this.o.questions.forEach((ea) => {
        if (ea._id === data.question) {
          ea.questions.forEach((q) => {
            if (q._id === data._id) {
              q = data
            }
          })
        }
      })
    })
  }

  // must delete question, and delete reference to it in question
  delete (question, questionID) {
    this.http.delete(`/api/questions/${question._id}`)
    console.log('successfully deleted q')
  }

  // editquestion: function(id, question) {
  //   question.questions.forEach(function(ea, i) {
  //     question.questions[i] = ea._id;
  //   });

  //   return this.http.put('/api/questions/' + id, question).then(function(data) {
  //     //var index = o.questions.indexOf(data._id);
  //     o.questions.forEach(function(ea) {
  //       if (ea._id === data._id) {
  //         angular.copy(data, ea);
  //       }
  //     });
  //   });
  // }
}

Questions.$inject = ['$http']

export default angular.module('bitfit.services.questions', [])
  .service('Questions', Questions)
  .name
<|endoftext|>