<commit_before>/*

    An [Express.js](http://expressjs.com/) instance represented as a Fluid component.  See the documentation for details:

    https://github.com/GPII/gpii-express/blob/master/docs/express.md

*/
"use strict";
var fluid = require("infusion");
var gpii  = fluid.registerNamespace("gpii");

var express  = require("express");

fluid.registerNamespace("gpii.express");

/**
 *
 * @param mapOrArray {Object} - A map or array to be sorted.
 * @returns {Array} - An array of elements, ordered by namespace and priority.
 *
 * Sorts an array or map by `priority`: http://docs.fluidproject.org/infusion/development/Priorities.html
 *
 * As maps do not preserve order, an array is always returned.  When used with a map, the keys will be discarded.
 *
 */
gpii.express.orderByPriority = function (mapOrArray) {
    var array = fluid.hashToArray(mapOrArray, "namespace", function (newEl, el) {
        newEl = fluid.censorKeys(el, ["priority"]);
        newEl.priority = fluid.parsePriority(el.priority, 0, false);
        return newEl;
    });

    fluid.sortByPriority(array);
    return array;
};

/**
 *
 * @param that {Object} - The `gpii.express` instance itself.
 *
 * Look up the path (parent, sibling, child) relationships for a node.  Used in constructing the hierarchy we will
 * use to wire together everything in our init method.
 *
 */
gpii.express.pathForComponent = function (that) {
    var instantiator  = fluid.getInstantiator(that);
    var path          = instantiator.idToPath(that.id);

    return fluid.model.parseEL(path);
};

/**
 *
 * @param childComponent
 * @param expressComponent
 *
 * Make a note of the lineage of this middleware or router component, all the way back presumably to the `gpii.express`
 * instance itself.  This function is distributed as a listener to child `gpii.express.router` and
 * `gpii.express.middleware` components, which use it to register themselves.
 *
 * The registry gathered by this process will be used to wire together all nested routers and any middleware
 * dependencies they have.
 *
 */
gpii.express.registerComponentLineage = function (childComponent, expressComponent) {
    // Get the list of parents for this component
    var segments = gpii.express.pathForComponent(childComponent);

    var childNickname  = segments[segments.length - 1];

    var expressSegments = gpii.express.pathForComponent(expressComponent);

    // If we only have one segment more than express, we are a child of express itself.
    // We need this to be able to handle cases in which express itself is a child component.
    if (segments.length === expressSegments.length + 1) {
        expressComponent.directChildrenOfInterest.push(childNickname);
    }
    // Otherwise, register the nickname of this component and its immediate parent path, each level will take care of the next highest.
    else if (segments.length > expressSegments.length + 1) {
        var parentPath = segments.slice(expressSegments.length, segments.length - 1).join(".");
        if (expressComponent.childrenByParent[parentPath]) {
            expressComponent.childrenByParent[parentPath].push(childNickname);
        }
        else {
            expressComponent.childrenByParent[parentPath] = [childNickname];
        }
    }
};

/**
 *
 * @param entry {Object} - A single entry object, which is expected to contain a `nick` variable.
 * @returns {String} - The contents of the `nick` variable.
 *
 * Static function for use with `Array.prototype.map`.  Extracts just the "nick" variable from the structure we construct in `getOrderedNicknames`.
 *
 */
gpii.express.extractNicks = function (entry) {
    return entry.nick;
};

/**
 *
 * @param nicknames {Object} - An unordered array of "nicknames" representing children of a given component.
 * @param component {Object} - The component we will resolve the "nicknames" against.
 * @returns {Array} - An array that contains all of the original "nicknames", but ordered by priority and namespace.
 *
 * Start with the nicknames each child component registered with us and combine it with the relevant priority data to
 * produce an ordered array.
 *
 */
gpii.express.getOrderedNicknames = function (nicknames, component) {
    // Create an array that combines the "nicknames" in `that.childrenByParent[path]` with each component's hints about
    // `priority` and `namespace`.
    var nickNamesWithComponentPrioritiesAndNamespaces = [];
    fluid.each(nicknames, function (childNickname) {
        var childComponent = component[childNickname];
        nickNamesWithComponentPrioritiesAndNamespaces.push({ nick: childNickname, namespace: childComponent.options.namespace, priority: childComponent.options.priority });
    });

    // Order the combined data by priority so that we have predictable control over which is loaded when, then map it
    // back to a simple ordered array.
    return gpii.express.orderByPriority(nickNamesWithComponentPrioritiesAndNamespaces).map(gpii.express.extractNicks);
};

/**
 *
 * @param that {Object} - The `gpii.express` instance itself.
 * @param component {Object} - The component we are working with at this level.  As we are treeing down through a hierarchy, this will change depending on the level we're at.
 * @param path {String} - The path segment where we can fine the child components we need to wire in to ourselves.
 *
 * Wire a child component to its immediate descendants using the `use` method common to both `express` and all
 * middleware.
 *
 */
gpii.express.connectDirectDescendants = function (that, component, path) {
    var orderedNicknames = gpii.express.getOrderedNicknames(that.childrenByParent[path], component);

    // This component has descendants, wire them in first.
    fluid.each(orderedNicknames, function (childNickname) {
        var childComponent = component[childNickname];

        if (fluid.hasGrade(childComponent.options, "gpii.express.middleware")) {
            gpii.express.wireMiddlewareToContainer(component.router, childComponent);
        }

        if (fluid.hasGrade(childComponent.options, "gpii.express.router")) {
            // Recurse from here on down.
            that.connectDirectDescendants(childComponent, path + "." + childNickname);
        }
    });
};

gpii.express.wireMiddlewareToContainer = function (container, middlewareComponent) {
    if (fluid.hasGrade(middlewareComponent.options, "gpii.express.middleware")) {
        fluid.each(fluid.makeArray(middlewareComponent.options.method), function (methodName) {
            container[methodName](middlewareComponent.options.path, middlewareComponent.getMiddlewareFn());
        });
    }
};

/**
 *
 * @param that {Object} - The `gpii.express` component itself.
 *
 * Create, configure and start our internal instance of `express`.  Wire up all middleware and routers.
 *
 */
gpii.express.init = function (that) {
    if (!that.options.port) {
        fluid.fail("Cannot initialize express because you have not supplied a 'port' option.");
    }
    else {
        that.express = express();

        var orderedNicknames = gpii.express.getOrderedNicknames(that.directChildrenOfInterest, that);

        // Wire together all routers and components, beginning with ourselves
        // for (var a = 0; a < that.directChildrenOfInterest.length; a++) {
        fluid.each(orderedNicknames, function (directChildNickname) {
            var childComponent  = that[directChildNickname];

            gpii.express.wireMiddlewareToContainer(that.express, childComponent);

            // Recurse from here on down.
            that.connectDirectDescendants(childComponent, directChildNickname);
        });

        that.express.set("port", that.options.port);
        that.server = that.express.listen(that.options.port, function () {
            fluid.log("Express server listening on port " + that.express.get("port"));

            fluid.log("Express started...");
            that.events.onStarted.fire(that.express, that);
        });
    }
};

/**
 *
 * @param that {Object} = The `gpii.express` component itself.
 *
 * Stop our internal instance of `express` when our component is destroyed.
 *
 */
gpii.express.stopServer = function (that) {
    that.server.close(function () {
        fluid.log("Express stopped...");
        that.events.onStopped.fire();
    });
};

/**
 *
 * @param array {Object} - An array of strings.
 * @returns {Array} - A new array with all references resolved.  The order of elements is preserved.
 *
 * Resolves package references (e. g. `%package-name/path/within/package/`) in an array of strings, which are presumed
 * to represent filesystem paths.
 *
 */
gpii.express.expandPaths = function (array) {
    return fluid.transform(fluid.makeArray(array), fluid.module.resolvePath);
};

fluid.defaults("gpii.express", {
    gradeNames: ["fluid.modelComponent", "gpii.express.expressConfigHolder"],
    members: {
        directChildrenOfInterest: [],
        childrenByParent:         {}
    },
    path: "/",
    express: null,
    distributeOptions: [
        {
            record: {
                "funcName": "gpii.express.registerComponentLineage",
                "args": ["{arguments}.0", "{gpii.express}"]
            },
            target: "{that gpii.express.middleware}.options.listeners.onReady"
        }
    ],
    invokers: {
        "connectDirectDescendants" : {
            "funcName": "gpii.express.connectDirectDescendants",
            "args":     ["{that}", "{arguments}.0", "{arguments}.1"]
        }
    },
    events: {
        onStarted:  null,
        onStopped:  null
    },
    listeners: {
        onCreate:
        {
            "funcName": "gpii.express.init",
            "args":     "{that}"
        },
        "onDestroy.stopServer":
        {
            funcName: "gpii.express.stopServer",
            args:     ["{that}"]
        }
    }
});

<commit_msg>GPII-1682:  Fixed decidedly un-fine typo.<commit_after>/*

    An [Express.js](http://expressjs.com/) instance represented as a Fluid component.  See the documentation for details:

    https://github.com/GPII/gpii-express/blob/master/docs/express.md

*/
"use strict";
var fluid = require("infusion");
var gpii  = fluid.registerNamespace("gpii");

var express  = require("express");

fluid.registerNamespace("gpii.express");

/**
 *
 * @param mapOrArray {Object} - A map or array to be sorted.
 * @returns {Array} - An array of elements, ordered by namespace and priority.
 *
 * Sorts an array or map by `priority`: http://docs.fluidproject.org/infusion/development/Priorities.html
 *
 * As maps do not preserve order, an array is always returned.  When used with a map, the keys will be discarded.
 *
 */
gpii.express.orderByPriority = function (mapOrArray) {
    var array = fluid.hashToArray(mapOrArray, "namespace", function (newEl, el) {
        newEl = fluid.censorKeys(el, ["priority"]);
        newEl.priority = fluid.parsePriority(el.priority, 0, false);
        return newEl;
    });

    fluid.sortByPriority(array);
    return array;
};

/**
 *
 * @param that {Object} - The `gpii.express` instance itself.
 *
 * Look up the path (parent, sibling, child) relationships for a node.  Used in constructing the hierarchy we will
 * use to wire together everything in our init method.
 *
 */
gpii.express.pathForComponent = function (that) {
    var instantiator  = fluid.getInstantiator(that);
    var path          = instantiator.idToPath(that.id);

    return fluid.model.parseEL(path);
};

/**
 *
 * @param childComponent
 * @param expressComponent
 *
 * Make a note of the lineage of this middleware or router component, all the way back presumably to the `gpii.express`
 * instance itself.  This function is distributed as a listener to child `gpii.express.router` and
 * `gpii.express.middleware` components, which use it to register themselves.
 *
 * The registry gathered by this process will be used to wire together all nested routers and any middleware
 * dependencies they have.
 *
 */
gpii.express.registerComponentLineage = function (childComponent, expressComponent) {
    // Get the list of parents for this component
    var segments = gpii.express.pathForComponent(childComponent);

    var childNickname  = segments[segments.length - 1];

    var expressSegments = gpii.express.pathForComponent(expressComponent);

    // If we only have one segment more than express, we are a child of express itself.
    // We need this to be able to handle cases in which express itself is a child component.
    if (segments.length === expressSegments.length + 1) {
        expressComponent.directChildrenOfInterest.push(childNickname);
    }
    // Otherwise, register the nickname of this component and its immediate parent path, each level will take care of the next highest.
    else if (segments.length > expressSegments.length + 1) {
        var parentPath = segments.slice(expressSegments.length, segments.length - 1).join(".");
        if (expressComponent.childrenByParent[parentPath]) {
            expressComponent.childrenByParent[parentPath].push(childNickname);
        }
        else {
            expressComponent.childrenByParent[parentPath] = [childNickname];
        }
    }
};

/**
 *
 * @param entry {Object} - A single entry object, which is expected to contain a `nick` variable.
 * @returns {String} - The contents of the `nick` variable.
 *
 * Static function for use with `Array.prototype.map`.  Extracts just the "nick" variable from the structure we construct in `getOrderedNicknames`.
 *
 */
gpii.express.extractNicks = function (entry) {
    return entry.nick;
};

/**
 *
 * @param nicknames {Object} - An unordered array of "nicknames" representing children of a given component.
 * @param component {Object} - The component we will resolve the "nicknames" against.
 * @returns {Array} - An array that contains all of the original "nicknames", but ordered by priority and namespace.
 *
 * Start with the nicknames each child component registered with us and combine it with the relevant priority data to
 * produce an ordered array.
 *
 */
gpii.express.getOrderedNicknames = function (nicknames, component) {
    // Create an array that combines the "nicknames" in `that.childrenByParent[path]` with each component's hints about
    // `priority` and `namespace`.
    var nickNamesWithComponentPrioritiesAndNamespaces = [];
    fluid.each(nicknames, function (childNickname) {
        var childComponent = component[childNickname];
        nickNamesWithComponentPrioritiesAndNamespaces.push({ nick: childNickname, namespace: childComponent.options.namespace, priority: childComponent.options.priority });
    });

    // Order the combined data by priority so that we have predictable control over which is loaded when, then map it
    // back to a simple ordered array.
    return gpii.express.orderByPriority(nickNamesWithComponentPrioritiesAndNamespaces).map(gpii.express.extractNicks);
};

/**
 *
 * @param that {Object} - The `gpii.express` instance itself.
 * @param component {Object} - The component we are working with at this level.  As we are treeing down through a hierarchy, this will change depending on the level we're at.
 * @param path {String} - The path segment where we can find the child components we need to wire in to ourselves.
 *
 * Wire a child component to its immediate descendants using the `use` method common to both `express` and all
 * middleware.
 *
 */
gpii.express.connectDirectDescendants = function (that, component, path) {
    var orderedNicknames = gpii.express.getOrderedNicknames(that.childrenByParent[path], component);

    // This component has descendants, wire them in first.
    fluid.each(orderedNicknames, function (childNickname) {
        var childComponent = component[childNickname];

        if (fluid.hasGrade(childComponent.options, "gpii.express.middleware")) {
            gpii.express.wireMiddlewareToContainer(component.router, childComponent);
        }

        if (fluid.hasGrade(childComponent.options, "gpii.express.router")) {
            // Recurse from here on down.
            that.connectDirectDescendants(childComponent, path + "." + childNickname);
        }
    });
};

gpii.express.wireMiddlewareToContainer = function (container, middlewareComponent) {
    if (fluid.hasGrade(middlewareComponent.options, "gpii.express.middleware")) {
        fluid.each(fluid.makeArray(middlewareComponent.options.method), function (methodName) {
            container[methodName](middlewareComponent.options.path, middlewareComponent.getMiddlewareFn());
        });
    }
};

/**
 *
 * @param that {Object} - The `gpii.express` component itself.
 *
 * Create, configure and start our internal instance of `express`.  Wire up all middleware and routers.
 *
 */
gpii.express.init = function (that) {
    if (!that.options.port) {
        fluid.fail("Cannot initialize express because you have not supplied a 'port' option.");
    }
    else {
        that.express = express();

        var orderedNicknames = gpii.express.getOrderedNicknames(that.directChildrenOfInterest, that);

        // Wire together all routers and components, beginning with ourselves
        // for (var a = 0; a < that.directChildrenOfInterest.length; a++) {
        fluid.each(orderedNicknames, function (directChildNickname) {
            var childComponent  = that[directChildNickname];

            gpii.express.wireMiddlewareToContainer(that.express, childComponent);

            // Recurse from here on down.
            that.connectDirectDescendants(childComponent, directChildNickname);
        });

        that.express.set("port", that.options.port);
        that.server = that.express.listen(that.options.port, function () {
            fluid.log("Express server listening on port " + that.express.get("port"));

            fluid.log("Express started...");
            that.events.onStarted.fire(that.express, that);
        });
    }
};

/**
 *
 * @param that {Object} = The `gpii.express` component itself.
 *
 * Stop our internal instance of `express` when our component is destroyed.
 *
 */
gpii.express.stopServer = function (that) {
    that.server.close(function () {
        fluid.log("Express stopped...");
        that.events.onStopped.fire();
    });
};

/**
 *
 * @param array {Object} - An array of strings.
 * @returns {Array} - A new array with all references resolved.  The order of elements is preserved.
 *
 * Resolves package references (e. g. `%package-name/path/within/package/`) in an array of strings, which are presumed
 * to represent filesystem paths.
 *
 */
gpii.express.expandPaths = function (array) {
    return fluid.transform(fluid.makeArray(array), fluid.module.resolvePath);
};

fluid.defaults("gpii.express", {
    gradeNames: ["fluid.modelComponent", "gpii.express.expressConfigHolder"],
    members: {
        directChildrenOfInterest: [],
        childrenByParent:         {}
    },
    path: "/",
    express: null,
    distributeOptions: [
        {
            record: {
                "funcName": "gpii.express.registerComponentLineage",
                "args": ["{arguments}.0", "{gpii.express}"]
            },
            target: "{that gpii.express.middleware}.options.listeners.onReady"
        }
    ],
    invokers: {
        "connectDirectDescendants" : {
            "funcName": "gpii.express.connectDirectDescendants",
            "args":     ["{that}", "{arguments}.0", "{arguments}.1"]
        }
    },
    events: {
        onStarted:  null,
        onStopped:  null
    },
    listeners: {
        onCreate:
        {
            "funcName": "gpii.express.init",
            "args":     "{that}"
        },
        "onDestroy.stopServer":
        {
            funcName: "gpii.express.stopServer",
            args:     ["{that}"]
        }
    }
});

<|endoftext|>


<commit_before>/**
* DiscordForge - a plugin system for Discord.
* Copyright (C) 2017 DiscordForge Development
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// deps
const fs = require('fs');
const path = require('path');
const os = require('os');

const pluginsDir = path.join(os.homedir(), '.discordforge/plugins');

const log = function(str) {
    console.log('%c[DiscordForge] ' + '%c' + str, 'color:#4286f4', 'color:auto');
};

// notify (in devtools) that we've loaded in
log('Loaded in!');
// and now to load plugins
log('Loading plugins...');
fs.readdirSync(pluginsDir).forEach(file => {
    if (file.endsWith('.plugin.js')) {
        log('Load: ' + file);
        try {
			let p = require(path.join(pluginsDir, file));
			p.init();
		} catch (e) {
			log(file + ' is not a valid plugin. Skipping.');
		}
    }
});<commit_msg>fix weird indent<commit_after>/**
* DiscordForge - a plugin system for Discord.
* Copyright (C) 2017 DiscordForge Development
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// deps
const fs = require('fs');
const path = require('path');
const os = require('os');

const pluginsDir = path.join(os.homedir(), '.discordforge/plugins');

const log = function(str) {
    console.log('%c[DiscordForge] ' + '%c' + str, 'color:#4286f4', 'color:auto');
};

// notify (in devtools) that we've loaded in
log('Loaded in!');
// and now to load plugins
log('Loading plugins...');
fs.readdirSync(pluginsDir).forEach(file => {
    if (file.endsWith('.plugin.js')) {
        log('Load: ' + file);
        try {
            let p = require(path.join(pluginsDir, file));
            p.init();
        } catch (e) {
            log(file + ' is not a valid plugin. Skipping.');
        }
    }
});<|endoftext|>


<commit_before>import "../css/geo-dash.css";

import React from "react";
import ReactDOM from "react-dom";

import {Feature} from "ol";
import {buffer as ExtentBuffer} from "ol/extent";
import {Circle, Point} from "ol/geom";
import {transform as projTransform} from "ol/proj";
import {fromExtent} from "ol/geom/Polygon";
import {Vector} from "ol/source";

import GeoDashNavigationBar from "./geodash/GeoDashNavigationBar";
import WidgetGridItem from "./geodash/WidgetGridItem";

import {mercator} from "./utils/mercator";
import {isArray, isNumber} from "./utils/generalUtils";
import {gridRowHeight} from "./geodash/constants";

class Geodash extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            widgets: [],
            mapCenter: null,
            mapZoom: null,
            imageryList: [],
            initCenter: null,
            initZoom: null,
            vectorSource: null
        };
    }

    /// Lifecycle

    componentDidMount() {
        Promise.all([this.getInstitutionImagery(), this.getWidgetsByProjectId(), this.getVectorSource()])
            .then(data => this.setState({imageryList: data[0], widgets: data[1], vectorSource: data[2]}))
            .catch(response => {
                console.log(response);
                alert("Error initializing Geo-Dash. See console for details.");
            });
    }

    /// API

    getInstitutionImagery = () => fetch(`/get-institution-imagery?institutionId=${this.props.institutionId}`)
        .then(response => (response.ok ? response.json() : Promise.reject(response)));

    getWidgetsByProjectId = () => fetch(`/geo-dash/get-project-widgets?projectId=${this.props.projectId}`)
        .then(response => (response.ok ? response.json() : Promise.reject(response)));

    getFeatures = async () => {
        const {plotShape, radius, center, plotId} = this.props;
        if (plotShape === "polygon") {
            const plotJsonObject = await fetch(`/get-plot-sample-geom?plotId=${plotId}`)
                .then(response => (response.ok ? response.json() : Promise.reject(response)));
            return [plotJsonObject.plotGeom, ...(plotJsonObject.sampleGeoms || [])]
                .filter(e => e)
                .map(geom => new Feature({geometry: mercator.parseGeoJson(geom, true)}));
        } else if (plotShape === "square") {
            const point = new Point(projTransform(JSON.parse(center).coordinates, "EPSG:4326", "EPSG:3857"));
            const bufferedExtent = new ExtentBuffer(point.getExtent(), radius);
            return [new Feature(fromExtent(bufferedExtent))];
        } else if (plotShape === "circle") {
            return [
                new Feature(
                    new Circle(
                        projTransform(JSON.parse(center).coordinates, "EPSG:4326", "EPSG:3857"),
                        Number(radius)
                    )
                )];
        } else {
            return [];
        }
    };

    getVectorSource = async () => new Vector({features: await this.getFeatures()});

    /// State

    setCenterAndZoom = (center, zoom) => {
        if (isArray(center) && isNumber(zoom)) {
            if (this.state.initCenter) {
                this.setState({
                    mapCenter: center,
                    mapZoom: zoom
                });
            } else {
                this.setState({
                    initCenter: center,
                    initZoom: zoom,
                    mapCenter: center,
                    mapZoom: zoom
                });
            }
        }
    };

    resetCenterAndZoom = () => {
        this.setCenterAndZoom(this.state.initCenter, this.state.initZoom);
    };

    /// Helpers

    extentToPolygon = extent => {
        const extentArray = JSON.parse(decodeURI(extent));
        return [
            [extentArray[0], extentArray[1]],
            [extentArray[2], extentArray[1]],
            [extentArray[2], extentArray[3]],
            [extentArray[0], extentArray[3]],
            [extentArray[0], extentArray[1]]
        ];
    };

    /// Render

    render() {
        const {widgets} = this.state;
        return (
            <div
                style={{
                    margin: ".75rem 1rem 3rem",
                    textAlign: "center",
                    display: "grid",
                    gridTemplateColumns: "repeat(12, 1fr)",
                    gridAutoRows: `${gridRowHeight}px`,
                    gap: ".5rem"
                }}
            >
                {widgets.length > 0
                    ? (widgets.map((widget, idx) => (
                        <WidgetGridItem
                            key={widget.id}
                            idx={idx}
                            imageryList={this.state.imageryList}
                            initCenter={this.mapCenter}
                            mapCenter={this.state.mapCenter}
                            mapZoom={this.state.mapZoom}
                            plotExtentPolygon={this.extentToPolygon(this.props.plotExtent)}
                            resetCenterAndZoom={this.resetCenterAndZoom}
                            setCenterAndZoom={this.setCenterAndZoom}
                            vectorSource={this.state.vectorSource}
                            visiblePlotId={this.props.visiblePlotId}
                            widget={widget}
                        />
                    ))
                    ) : (

There is no Geo-Dash widgets for this project

                    )}

        );
    }
}

export function pageInit(args) {
    ReactDOM.render(
        <GeoDashNavigationBar
            page={() => <Geodash {...args}/>}
            userName={args.userName || "guest"}
            visiblePlotId={args.visiblePlotId ? parseInt(args.visiblePlotId) : -1}
        />,
        document.getElementById("app")
    );
}
<commit_msg>Show loading message when retrieving widgets<commit_after>import "../css/geo-dash.css";

import React from "react";
import ReactDOM from "react-dom";

import {Feature} from "ol";
import {buffer as ExtentBuffer} from "ol/extent";
import {Circle, Point} from "ol/geom";
import {transform as projTransform} from "ol/proj";
import {fromExtent} from "ol/geom/Polygon";
import {Vector} from "ol/source";

import GeoDashNavigationBar from "./geodash/GeoDashNavigationBar";
import WidgetGridItem from "./geodash/WidgetGridItem";

import {mercator} from "./utils/mercator";
import {isArray, isNumber} from "./utils/generalUtils";
import {gridRowHeight} from "./geodash/constants";

class Geodash extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            widgets: null,
            mapCenter: null,
            mapZoom: null,
            imageryList: [],
            initCenter: null,
            initZoom: null,
            vectorSource: null
        };
    }

    /// Lifecycle

    componentDidMount() {
        Promise.all([this.getInstitutionImagery(), this.getWidgetsByProjectId(), this.getVectorSource()])
            .then(data => this.setState({imageryList: data[0], widgets: data[1], vectorSource: data[2]}))
            .catch(response => {
                console.log(response);
                alert("Error initializing Geo-Dash. See console for details.");
            });
    }

    /// API

    getInstitutionImagery = () => fetch(`/get-institution-imagery?institutionId=${this.props.institutionId}`)
        .then(response => (response.ok ? response.json() : Promise.reject(response)));

    getWidgetsByProjectId = () => fetch(`/geo-dash/get-project-widgets?projectId=${this.props.projectId}`)
        .then(response => (response.ok ? response.json() : Promise.reject(response)));

    getFeatures = async () => {
        const {plotShape, radius, center, plotId} = this.props;
        if (plotShape === "polygon") {
            const plotJsonObject = await fetch(`/get-plot-sample-geom?plotId=${plotId}`)
                .then(response => (response.ok ? response.json() : Promise.reject(response)));
            return [plotJsonObject.plotGeom, ...(plotJsonObject.sampleGeoms || [])]
                .filter(e => e)
                .map(geom => new Feature({geometry: mercator.parseGeoJson(geom, true)}));
        } else if (plotShape === "square") {
            const point = new Point(projTransform(JSON.parse(center).coordinates, "EPSG:4326", "EPSG:3857"));
            const bufferedExtent = new ExtentBuffer(point.getExtent(), radius);
            return [new Feature(fromExtent(bufferedExtent))];
        } else if (plotShape === "circle") {
            return [
                new Feature(
                    new Circle(
                        projTransform(JSON.parse(center).coordinates, "EPSG:4326", "EPSG:3857"),
                        Number(radius)
                    )
                )];
        } else {
            return [];
        }
    };

    getVectorSource = async () => new Vector({features: await this.getFeatures()});

    /// State

    setCenterAndZoom = (center, zoom) => {
        if (isArray(center) && isNumber(zoom)) {
            if (this.state.initCenter) {
                this.setState({
                    mapCenter: center,
                    mapZoom: zoom
                });
            } else {
                this.setState({
                    initCenter: center,
                    initZoom: zoom,
                    mapCenter: center,
                    mapZoom: zoom
                });
            }
        }
    };

    resetCenterAndZoom = () => {
        this.setCenterAndZoom(this.state.initCenter, this.state.initZoom);
    };

    /// Helpers

    extentToPolygon = extent => {
        const extentArray = JSON.parse(decodeURI(extent));
        return [
            [extentArray[0], extentArray[1]],
            [extentArray[2], extentArray[1]],
            [extentArray[2], extentArray[3]],
            [extentArray[0], extentArray[3]],
            [extentArray[0], extentArray[1]]
        ];
    };

    /// Render

    render() {
        const {widgets} = this.state;
        return (
            <div
                style={{
                    margin: ".75rem 1rem 3rem",
                    textAlign: "center",
                    display: "grid",
                    gridTemplateColumns: "repeat(12, 1fr)",
                    gridAutoRows: `${gridRowHeight}px`,
                    gap: ".5rem"
                }}
            >
                {widgets === null ? (

Retrieving Geo-Dash configuration for this project

                ) : widgets.length > 0
                    ? (widgets.map((widget, idx) => (
                        <WidgetGridItem
                            key={widget.id}
                            idx={idx}
                            imageryList={this.state.imageryList}
                            initCenter={this.mapCenter}
                            mapCenter={this.state.mapCenter}
                            mapZoom={this.state.mapZoom}
                            plotExtentPolygon={this.extentToPolygon(this.props.plotExtent)}
                            resetCenterAndZoom={this.resetCenterAndZoom}
                            setCenterAndZoom={this.setCenterAndZoom}
                            vectorSource={this.state.vectorSource}
                            visiblePlotId={this.props.visiblePlotId}
                            widget={widget}
                        />
                    ))
                    ) : (

There is no Geo-Dash widgets for this project

                    )}

        );
    }
}

export function pageInit(args) {
    ReactDOM.render(
        <GeoDashNavigationBar
            page={() => <Geodash {...args}/>}
            userName={args.userName || "guest"}
            visiblePlotId={args.visiblePlotId ? parseInt(args.visiblePlotId) : -1}
        />,
        document.getElementById("app")
    );
}
<|endoftext|>


<commit_before>(function () {
  angular.module('ui.grid').config(['$provide', function($provide) {
    $provide.decorator('i18nService', ['$delegate', function($delegate) {
      $delegate.add('no', {
        headerCell: {
          aria: {
            defaultFilterLabel: 'Filter for column',
            removeFilter: 'Remove Filter',
            columnMenuButtonLabel: 'Column Menu'
          },
          priority: 'Priority:',
          filterLabel: "Filter for column: "
        },
        aggregate: {
          label: 'items'
        },
        groupPanel: {
          description: 'Drag a column header here and drop it to group by that column.'
        },
        search: {
          placeholder: 'Search...',
          showingItems: 'Showing Items:',
          selectedItems: 'Selected Items:',
          totalItems: 'Total Items:',
          size: 'Page Size:',
          first: 'First Page',
          next: 'Next Page',
          previous: 'Previous Page',
          last: 'Last Page'
        },
        menu: {
          text: 'Choose Columns:'
        },
        sort: {
          ascending: 'Sort Ascending',
          descending: 'Sort Descending',
          none: 'Sort None',
          remove: 'Remove Sort'
        },
        column: {
          hide: 'Hide Column'
        },
        aggregation: {
          count: 'total rows: ',
          sum: 'total: ',
          avg: 'avg: ',
          min: 'min: ',
          max: 'max: '
        },
        pinning: {
          pinLeft: 'Pin Left',
          pinRight: 'Pin Right',
          unpin: 'Unpin'
        },
        columnMenu: {
          close: 'Close'
        },
        gridMenu: {
          aria: {
            buttonLabel: 'Grid Menu'
          },
          columns: 'Kolonner:',
          importerTitle: 'Importer fil',
          exporterAllAsCsv: 'Eksporter alle data som csv',
          exporterVisibleAsCsv: 'Eksporter synlige data som csv',
          exporterSelectedAsCsv: 'Eksporter utvalgte data som csv',
          exporterAllAsPdf: 'Eksporter alle data som pdf',
          exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
          exporterSelectedAsPdf: 'Eksporter utvalgte data som pdf',
          clearAllFilters: 'Clear all filters'
        },
        importer: {
          noHeaders: 'Column names were unable to be derived, does the file have a header?',
          noObjects: 'Objects were not able to be derived, was there data in the file other than headers?',
          invalidCsv: 'File was unable to be processed, is it valid CSV?',
          invalidJson: 'File was unable to be processed, is it valid Json?',
          jsonNotArray: 'Imported json file must contain an array, aborting.'
        },
        pagination: {
          aria: {
            pageToFirst: 'Page to first',
            pageBack: 'Page back',
            pageSelected: 'Selected page',
            pageForward: 'Page forward',
            pageToLast: 'Page to last'
          },
          sizes: 'items per page',
          totalItems: 'items',
          through: 'through',
          of: 'of'
        },
        grouping: {
          group: 'Group',
          ungroup: 'Ungroup',
          aggregate_count: 'Agg: Count',
          aggregate_sum: 'Agg: Sum',
          aggregate_max: 'Agg: Max',
          aggregate_min: 'Agg: Min',
          aggregate_avg: 'Agg: Avg',
          aggregate_remove: 'Agg: Remove'
        }
      });
      return $delegate;
    }]);
  }]);
})();
<commit_msg>fix(6061): Translated missing resources to Norwegian in no.js<commit_after>(function () {
  angular.module('ui.grid').config(['$provide', function($provide) {
    $provide.decorator('i18nService', ['$delegate', function($delegate) {
      $delegate.add('no', {
        headerCell: {
          aria: {
            defaultFilterLabel: 'Filter for kolonne',
            removeFilter: 'Fjern filter',
            columnMenuButtonLabel: 'Kolonnemeny'
          },
          priority: 'Prioritet:',
          filterLabel: "Filter for kolonne: "
        },
        aggregate: {
          label: 'elementer'
        },
        groupPanel: {
          description: 'Trekk en kolonneoverskrift hit og slipp den for å gruppere etter den kolonnen.'
        },
        search: {
          placeholder: 'Søk...',
          showingItems: 'Viste elementer:',
          selectedItems: 'Valgte elementer:',
          totalItems: 'Antall elementer:',
          size: 'Sidestørrelse:',
          first: 'Første side',
          next: 'Neste side',
          previous: 'Forrige side',
          last: 'Siste side'
        },
        menu: {
          text: 'Velg kolonner:'
        },
        sort: {
          ascending: 'Sortere stigende',
          descending: 'Sortere fallende',
          none: 'Ingen sortering',
          remove: 'Fjern sortering'
        },
        column: {
          hide: 'Skjul kolonne'
        },
        aggregation: {
          count: 'antall rader: ',
          sum: 'total: ',
          avg: 'gjennomsnitt: ',
          min: 'minimum: ',
          max: 'maksimum: '
        },
        pinning: {
          pinLeft: 'Fest til venstre',
          pinRight: 'Fest til høyre',
          unpin: 'Løsne'
        },
        columnMenu: {
          close: 'Lukk'
        },
        gridMenu: {
          aria: {
            buttonLabel: 'Grid Menu'
          },
          columns: 'Kolonner:',
          importerTitle: 'Importer fil',
          exporterAllAsCsv: 'Eksporter alle data som csv',
          exporterVisibleAsCsv: 'Eksporter synlige data som csv',
          exporterSelectedAsCsv: 'Eksporter utvalgte data som csv',
          exporterAllAsPdf: 'Eksporter alle data som pdf',
          exporterVisibleAsPdf: 'Eksporter synlige data som pdf',
          exporterSelectedAsPdf: 'Eksporter utvalgte data som pdf',
          clearAllFilters: 'Clear all filters'
        },
        importer: {
          noHeaders: 'Kolonnenavn kunne ikke avledes. Har filen en overskrift?',
          noObjects: 'Objekter kunne ikke avledes. Er der andre data i filen enn overskriften?',
          invalidCsv: 'Filen kunne ikke behandles. Er den gyldig CSV?',
          invalidJson: 'Filen kunne ikke behandles. Er den gyldig JSON?',
          jsonNotArray: 'Importert JSON-fil må inneholde en liste. Avbryter.'
        },
        pagination: {
          aria: {
            pageToFirst: 'Gå til første side',
            pageBack: 'Gå til forrige side',
            pageSelected: 'Valgte side',
            pageForward: 'Gå til neste side',
            pageToLast: 'Gå til siste side'
          },
          sizes: 'elementer per side',
          totalItems: 'elementer',
          through: 'til',
          of: 'av'
        },
        grouping: {
          group: 'Gruppere',
          ungroup: 'Fjerne gruppering',
          aggregate_count: 'Agr: Antall',
          aggregate_sum: 'Agr: Sum',
          aggregate_max: 'Agr: Maksimum',
          aggregate_min: 'Agr: Minimum',
          aggregate_avg: 'Agr: Gjennomsnitt',
          aggregate_remove: 'Agr: Fjern'
        }
      });
      return $delegate;
    }]);
  }]);
})();
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012-2013 InSeven Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

(function($) {

  App.Library = function() {
    this.init();
  };

  App.Library.State = {
    UNINITIALIZED: 0,
    LOADING: 1,
    UNAUTHORIZED: 2,
    UPDATING: 3,
    READY: 4
  };

  App.Library.THUMBNAIL_TYPE = "jpg";

  jQuery.extend(
    App.Library.prototype, {

    init: function() {
      var self = this;
      self.state = App.Library.State.UNINITIALIZED;
      self.items = [];
      self.thumbnails = {};
      self.changeCallbacks = [];
      self.stateChangeCallbacks = [];
      self.drive = App.Drive.getInstance();
      self.thumbnailStore = new App.Store('thumbnails');

      // We use a separate flag to track updates internally as
      // we need to be able to schedule updates in many different states
      // due to the asynchronous nature of the update.
      self.updatePending = false;

      // Handle Google Drive state changes to update our state.
      self.drive.onStateChange(function(state) {
        if (state === App.Drive.State.UNAUTHORIZED) {
          self.setState(App.Library.State.UNAUTHORIZED);
        } else if (state === App.Drive.State.READY) {
          self.setState(App.Library.State.READY);
        } else {
          self.setState(App.Library.State.LOADING);
        }
      });

      // Load the library.
      var library = localStorage.getItem('library');
      if (library) {
        self.items = jQuery.parseJSON(library);
      }

      self.sort();

    },

    authorize: function() {
      var self = this;
      self.drive.authorize(false);
    },

    onStateChange: function(callback) {
      var self = this;
      self.stateChangeCallbacks.push(callback);
    },

    setState: function(state) {
      var self = this;
      if (self.state !== state) {
        self.state = state;

        // Fire the state change callbacks.
        for (var i = 0; i < self.stateChangeCallbacks.length; i++) {
          var callback = self.stateChangeCallbacks[i];
          callback(state);
        }

      }
    },

    onChange: function(callback) {
      var self = this;
      self.changeCallbacks.push(callback);
    },

    notifyChange: function() {
      var self = this;
      for (var i = 0; i < self.changeCallbacks.length; i++) {
        var callback = self.changeCallbacks[i];
        callback();
      }
    },

    sort: function() {
      var self = this;
      self.items.sort(function(a, b) {
        return a['title'].toLowerCase() > b['title'].toLowerCase() ? 1 : -1;
      });
    },

    save: function() {
      var self = this;
      localStorage.setItem('library', JSON.stringify(self.items));
    },

    count: function() {
      var self = this;
      return self.items.length;
    },

    titleForIndex: function(index) {
      var self = this;
      var file = self.items[index];
      return self.stripExtension(file.title);
    },

    // Very rudimentary mechanism to strip the file extension (Google Drive doesn't
    // seem to guarantee file extensions in the file title).
    stripExtension: function(title) {
      var self = this;
      return title.slice(0, title.lastIndexOf("."));
    },

    availableOffline: function(index) {
      var self = this;
      var identifier = self.identifierForIndex(index);
      var data = localStorage.getItem(identifier);
      if (data !== null) {
        return true;
      }
      return false;
    },

    identifierForIndex: function(index) {
      var self = this;
      var file = self.items[index];
      return file.id;
    },

    thumbnailForIndex: function(index, callback) {
      var self = this;
      var identifier = self.identifierForIndex(index);
      self.thumbnailForIdentifier(identifier, callback);
    },

    // Returns the thumbnail for a given identifier.
    // Triggers a fetch from the cache if the thumbnail is not present.
    thumbnailForIdentifier: function(identifier, callback) {
      var self = this;
      if (identifier in self.thumbnails) {
        callback(self.thumbnails[identifier]);
      } else {
        self.thumbnailStore.property("thumbnails", identifier, function(value) {
          if (value !== undefined) {
            self.thumbnails[identifier] = self.thumbnailDataUrl(value);
            callback(self.thumbnails[identifier]);
          }
        });
      }
    },

    update: function() {
      var self = this;
      // Only schedule a new update if we're not already updating.
      if (self.updatePending === false) {
        self.drive.files({
          'onStart': function() {
            self.setState(App.Library.State.UPDATING);
          },
          'onSuccess': function(files) {
            self.updateCallback(files);
          },
          'onError': function(error) {
            // Ignore the error.
            self.setState(App.Library.State.READY);
          }
        });
      }
    },

    fileForIdentifier: function(identifier) {
      var self = this;
      for (var i = 0; i < self.items.length; i++) {
        var file = self.items[i];
        if (file.id === identifier) {
          return file;
        }
      }
      return undefined;
    },

    fetch: function(identifier, callback) {
      var self = this;

      // Only attempt to download the file if it hasn't already been cached.
      var data = localStorage.getItem(identifier);
      if (data) {
        callback(data);
      } else {
        var file = self.fileForIdentifier(identifier);
        downloadFile(file, function(data) {
          //localStorage.setItem(file.id, data);
          self.notifyChange();
          callback(data);
        });
      }

    },

    // Converts a base64 encoded thumbnail image into a suitable URL.
    thumbnailDataUrl: function(data) {
      var self = this;
      return "data:image/" + App.Library.THUMBNAIL_TYPE + ";base64," + data;
    },

    thumbnail: function(index, callback) {
      var self = this;
      var identifier = self.identifierForIndex(index);

      self.thumbnailStore.property("thumbnails", identifier, function(value) {
        if (value !== undefined) {
          self.thumbnails[identifier] = self.thumbnailDataUrl(value);
          callback(self.thumbnails[identifier]);
        } else {

          file = self.fileForIdentifier(identifier);

          // Some files do not seem to have a valid parents array.
          // If they do not, then there is no reasonable way to hunt for a thumbnail.
          var parents = file.parents[0];
          if (parents === undefined) {
            return;
          }

          var parent = file.parents[0].id;
          var title = self.stripExtension(file.title) + "." + App.Library.THUMBNAIL_TYPE;

          console.log("Fetching " + title + " ...");

          self.drive.file(parent, title, {
            'onStart': function() {},
            'onSuccess': function(file) {
              if (file !== undefined) {
                downloadFileBase64(file, function(data) {
                  try {
                    self.thumbnailStore.setProperty("thumbnails", identifier, data);
                  } catch (e) {
                    console.log("Unable to store thumbnail.");
                  }
                  self.thumbnails[identifier] = self.thumbnailDataUrl(data);
                  callback(self.thumbnails[identifier]);
                });
              } else {
                callback();
              }
            },
            'onError': function(error) {
              callback();
            }
          });
        }
      });

    },

    // Checks to see if there is a cached thumbnail.  If no thumbnail has been cached,
    // one will be fetched from Google Drive if it's available.
    // This implementation caches all thumbnails in memory.  It's possible that this will
    // introduce performance issues, but it doesn't seem worthwhile loading everything lazily
    // at this stage.
    updateThumbnail: function(file, callback) {
      var self = this;

      var identifier = file.id;

      // Some files do not seem to have a valid parents array.
      // If they do not, then there is no reasonable way to hunt for a thumbnail.
      var parents = file.parents[0];
      if (parents === undefined) {
        return;
      }

      var parent = file.parents[0].id;
      var title = self.stripExtension(file.title) + "." + App.Library.THUMBNAIL_TYPE;

      // Don't bother re-fetching a thumbnail if we've already cached it.
      if (identifier in self.thumbnails) {
        return;
      }

      // Check the cache.
      self.thumbnailStore.property("thumbnails", identifier, function(value) {
        if (value !== undefined) {
          self.thumbnails[identifier] = self.thumbnailDataUrl(value);
          callback();
        } else {
          self.drive.file(parent, title, {
            'onStart': function() {},
            'onSuccess': function(file) {
              if (file !== undefined) {
                downloadFileBase64(file, function(data) {
                  self.thumbnailStore.setProperty("thumbnails", identifier, data);
                  self.thumbnails[identifier] = self.thumbnailDataUrl(data);
                  self.notifyChange();
                });
              }
              callback();
            },
            'onError': function(error) {
              callback();
            }
          });
        }
      });

    },

    updateCallback: function(files) {
      var self = this;
      var i;

      // Create an associative array of identifiers which are currently in the store.
      var deletedIdentifiers = {};
      for (i = 0; i < self.items.length; i++) {
        var identifier = self.items[i].id;
        deletedIdentifiers[identifier] = true;
      }

      // Update the thumbnails.
      for (i = 0; i < files.length; i++) {
        self.updateThumbnail(files[i], function() {});
      }

      // Reset the update flag.
      self.updatePending = false;

      // Update the files.
      self.items = [];
      for (i = 0; i < files.length; i++) {
        var file = files[i];
        if (file.fileExtension === 'gb' ||
            file.fileExtension === 'gbc') {
          // Cache the file.
          self.items.push(file);
          // Remove the item from the list of deleted identifiers.
          delete deletedIdentifiers[file.id];
        }
      }
      self.sort();
      self.save();

      // Clean up the deleted items.
      for (var key in deletedIdentifiers) {
        if (deletedIdentifiers.hasOwnProperty(key)) {
          localStorage.removeItem(key);
          self.thumbnailStore.deleteProperty(key);
        }
      }

      self.setState(App.Library.State.READY);
      self.notifyChange();
    }

  });

})(jQuery);
<commit_msg>Using a constant for the thumbnails domain.<commit_after>/*
 * Copyright (C) 2012-2013 InSeven Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

(function($) {

  App.Library = function() {
    this.init();
  };

  App.Library.State = {
    UNINITIALIZED: 0,
    LOADING: 1,
    UNAUTHORIZED: 2,
    UPDATING: 3,
    READY: 4
  };

  App.Library.THUMBNAIL_TYPE = "jpg";

  jQuery.extend(
    App.Library.prototype, {

    init: function() {
      var self = this;
      self.state = App.Library.State.UNINITIALIZED;
      self.items = [];
      self.thumbnails = {};
      self.changeCallbacks = [];
      self.stateChangeCallbacks = [];
      self.drive = App.Drive.getInstance();
      self.thumbnailStore = new App.Store('thumbnails');

      // We use a separate flag to track updates internally as
      // we need to be able to schedule updates in many different states
      // due to the asynchronous nature of the update.
      self.updatePending = false;

      // Handle Google Drive state changes to update our state.
      self.drive.onStateChange(function(state) {
        if (state === App.Drive.State.UNAUTHORIZED) {
          self.setState(App.Library.State.UNAUTHORIZED);
        } else if (state === App.Drive.State.READY) {
          self.setState(App.Library.State.READY);
        } else {
          self.setState(App.Library.State.LOADING);
        }
      });

      // Load the library.
      var library = localStorage.getItem('library');
      if (library) {
        self.items = jQuery.parseJSON(library);
      }

      self.sort();

    },

    authorize: function() {
      var self = this;
      self.drive.authorize(false);
    },

    onStateChange: function(callback) {
      var self = this;
      self.stateChangeCallbacks.push(callback);
    },

    setState: function(state) {
      var self = this;
      if (self.state !== state) {
        self.state = state;

        // Fire the state change callbacks.
        for (var i = 0; i < self.stateChangeCallbacks.length; i++) {
          var callback = self.stateChangeCallbacks[i];
          callback(state);
        }

      }
    },

    onChange: function(callback) {
      var self = this;
      self.changeCallbacks.push(callback);
    },

    notifyChange: function() {
      var self = this;
      for (var i = 0; i < self.changeCallbacks.length; i++) {
        var callback = self.changeCallbacks[i];
        callback();
      }
    },

    sort: function() {
      var self = this;
      self.items.sort(function(a, b) {
        return a['title'].toLowerCase() > b['title'].toLowerCase() ? 1 : -1;
      });
    },

    save: function() {
      var self = this;
      localStorage.setItem('library', JSON.stringify(self.items));
    },

    count: function() {
      var self = this;
      return self.items.length;
    },

    titleForIndex: function(index) {
      var self = this;
      var file = self.items[index];
      return self.stripExtension(file.title);
    },

    // Very rudimentary mechanism to strip the file extension (Google Drive doesn't
    // seem to guarantee file extensions in the file title).
    stripExtension: function(title) {
      var self = this;
      return title.slice(0, title.lastIndexOf("."));
    },

    availableOffline: function(index) {
      var self = this;
      var identifier = self.identifierForIndex(index);
      var data = localStorage.getItem(identifier);
      if (data !== null) {
        return true;
      }
      return false;
    },

    identifierForIndex: function(index) {
      var self = this;
      var file = self.items[index];
      return file.id;
    },

    thumbnailForIndex: function(index, callback) {
      var self = this;
      var identifier = self.identifierForIndex(index);
      self.thumbnailForIdentifier(identifier, callback);
    },

    // Returns the thumbnail for a given identifier.
    // Triggers a fetch from the cache if the thumbnail is not present.
    thumbnailForIdentifier: function(identifier, callback) {
      var self = this;
      if (identifier in self.thumbnails) {
        callback(self.thumbnails[identifier]);
      } else {
        self.thumbnailStore.property(App.Controller.Domain.THUMBNAILS, identifier, function(value) {
          if (value !== undefined) {
            self.thumbnails[identifier] = self.thumbnailDataUrl(value);
            callback(self.thumbnails[identifier]);
          }
        });
      }
    },

    update: function() {
      var self = this;
      // Only schedule a new update if we're not already updating.
      if (self.updatePending === false) {
        self.drive.files({
          'onStart': function() {
            self.setState(App.Library.State.UPDATING);
          },
          'onSuccess': function(files) {
            self.updateCallback(files);
          },
          'onError': function(error) {
            // Ignore the error.
            self.setState(App.Library.State.READY);
          }
        });
      }
    },

    fileForIdentifier: function(identifier) {
      var self = this;
      for (var i = 0; i < self.items.length; i++) {
        var file = self.items[i];
        if (file.id === identifier) {
          return file;
        }
      }
      return undefined;
    },

    fetch: function(identifier, callback) {
      var self = this;

      // Only attempt to download the file if it hasn't already been cached.
      var data = localStorage.getItem(identifier);
      if (data) {
        callback(data);
      } else {
        var file = self.fileForIdentifier(identifier);
        downloadFile(file, function(data) {
          //localStorage.setItem(file.id, data);
          self.notifyChange();
          callback(data);
        });
      }

    },

    // Converts a base64 encoded thumbnail image into a suitable URL.
    thumbnailDataUrl: function(data) {
      var self = this;
      return "data:image/" + App.Library.THUMBNAIL_TYPE + ";base64," + data;
    },

    thumbnail: function(index, callback) {
      var self = this;
      var identifier = self.identifierForIndex(index);

      self.thumbnailStore.property(App.Controller.Domain.THUMBNAILS, identifier, function(value) {
        if (value !== undefined) {
          self.thumbnails[identifier] = self.thumbnailDataUrl(value);
          callback(self.thumbnails[identifier]);
        } else {

          file = self.fileForIdentifier(identifier);

          // Some files do not seem to have a valid parents array.
          // If they do not, then there is no reasonable way to hunt for a thumbnail.
          var parents = file.parents[0];
          if (parents === undefined) {
            return;
          }

          var parent = file.parents[0].id;
          var title = self.stripExtension(file.title) + "." + App.Library.THUMBNAIL_TYPE;

          console.log("Fetching " + title + " ...");

          self.drive.file(parent, title, {
            'onStart': function() {},
            'onSuccess': function(file) {
              if (file !== undefined) {
                downloadFileBase64(file, function(data) {
                  try {
                    self.thumbnailStore.setProperty(App.Controller.Domain.THUMBNAILS, identifier, data);
                  } catch (e) {
                    console.log("Unable to store thumbnail.");
                  }
                  self.thumbnails[identifier] = self.thumbnailDataUrl(data);
                  callback(self.thumbnails[identifier]);
                });
              } else {
                callback();
              }
            },
            'onError': function(error) {
              callback();
            }
          });
        }
      });

    },

    // Checks to see if there is a cached thumbnail.  If no thumbnail has been cached,
    // one will be fetched from Google Drive if it's available.
    // This implementation caches all thumbnails in memory.  It's possible that this will
    // introduce performance issues, but it doesn't seem worthwhile loading everything lazily
    // at this stage.
    updateThumbnail: function(file, callback) {
      var self = this;

      var identifier = file.id;

      // Some files do not seem to have a valid parents array.
      // If they do not, then there is no reasonable way to hunt for a thumbnail.
      var parents = file.parents[0];
      if (parents === undefined) {
        return;
      }

      var parent = file.parents[0].id;
      var title = self.stripExtension(file.title) + "." + App.Library.THUMBNAIL_TYPE;

      // Don't bother re-fetching a thumbnail if we've already cached it.
      if (identifier in self.thumbnails) {
        return;
      }

      // Check the cache.
      self.thumbnailStore.property(App.Controller.Domain.THUMBNAILS, identifier, function(value) {
        if (value !== undefined) {
          self.thumbnails[identifier] = self.thumbnailDataUrl(value);
          callback();
        } else {
          self.drive.file(parent, title, {
            'onStart': function() {},
            'onSuccess': function(file) {
              if (file !== undefined) {
                downloadFileBase64(file, function(data) {
                  self.thumbnailStore.setProperty(App.Controller.Domain.THUMBNAILS, identifier, data);
                  self.thumbnails[identifier] = self.thumbnailDataUrl(data);
                  self.notifyChange();
                });
              }
              callback();
            },
            'onError': function(error) {
              callback();
            }
          });
        }
      });

    },

    updateCallback: function(files) {
      var self = this;
      var i;

      // Create an associative array of identifiers which are currently in the store.
      var deletedIdentifiers = {};
      for (i = 0; i < self.items.length; i++) {
        var identifier = self.items[i].id;
        deletedIdentifiers[identifier] = true;
      }

      // Update the thumbnails.
      for (i = 0; i < files.length; i++) {
        self.updateThumbnail(files[i], function() {});
      }

      // Reset the update flag.
      self.updatePending = false;

      // Update the files.
      self.items = [];
      for (i = 0; i < files.length; i++) {
        var file = files[i];
        if (file.fileExtension === 'gb' ||
            file.fileExtension === 'gbc') {
          // Cache the file.
          self.items.push(file);
          // Remove the item from the list of deleted identifiers.
          delete deletedIdentifiers[file.id];
        }
      }
      self.sort();
      self.save();

      // Clean up the deleted items.
      for (var key in deletedIdentifiers) {
        if (deletedIdentifiers.hasOwnProperty(key)) {
          localStorage.removeItem(key);
          self.thumbnailStore.deleteProperty(key);
        }
      }

      self.setState(App.Library.State.READY);
      self.notifyChange();
    }

  });

})(jQuery);
<|endoftext|>


<commit_before>
// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
  // Use default value color = 'red' and likesColor = true.
  chrome.storage.sync.get({
    // values to load
  }, function(items) {
    // TODO
  });
}

class OptionLevel extends HTMLElement{
  constructor(level, checked=false){
    super();

    var chkbx = document.createElement("input");
    var span = document.createElement("span");
    chkbx.type = "checkbox";
    chkbx.id = level.id;
    chkbx.value = level.name;
    chkbx.checked = checked;
    span.textContent = level.name;
    this.append(chkbx);
    this.append(span);
    return this;
  }
}
customElements.define('option-level', OptionLevel);

class OptionLevels extends HTMLElement{

  addOption(level){
    this.append(new OptionLevel(level));
  }

  constructor(levels){
    super();
    for(var level in levels)
      this.addOption({name:level, id:levels[level]});
    return this;
  }
}
customElements.define('option-levels', OptionLevels);

class OptionsSection extends HTMLElement{
  constructor(name, custom, id){
    super();

    var title = document.createElement("h2");
    title.textContent = this.getAttribute("name") || name || "Custom batch";
    this.append(title);

    if(custom) {
      this.id = id? id : new Date().valueOf();
      this.custom = true;
      var selected_levels = {};
      document.querySelectorAll("#main-section input:checked").forEach(function(input){
        selected_levels[input.value] = input.id;
      });
      var del = document.createElement("button");
      del.className = "delete";
      del.textContent = "x";
      del.addEventListener("click", function(){this.parentNode.remove();});

      var opt_levels = new OptionLevels(selected_levels);
      $(title).click(function(){ $(opt_levels).toggle();});
      this.appendChild(del);
      this.appendChild(opt_levels);

    }
  }
}
customElements.define('option-section', OptionsSection);

(function(){

  var ls_main_levels;
  var ls_main_conf;
  var ls_cust_batches;

  // Saves options to chrome.storage
  function save_options() {
    console.debug('saving options');

    var options = {
      "main_batch":{
        "levels" : ls_main_levels,
        "conf":{}
      },
      "cust_batches": []
    };

    // Get main_batch changes
    document.querySelectorAll("#main-section input:checked").forEach(function(item){options.main_batch.conf[item.value] = true;});

    // Get custom batches
    document.querySelectorAll("#custom-sections option-section").forEach(function(section){
      var publishable = true;
      var section_conf = {
        "id": section.id,
        "name": section.querySelector("h2").textContent,
        "conf": [],
        "publishable": true
      };
      section.querySelectorAll("input:checked").forEach(function(item){
        if(!item.value.match(/17/) && publishable) publishable = !publishable;
        section_conf.conf.push(item.id);
      });
      section_conf.publishable = publishable;

      options.cust_batches.push(section_conf);
    });

    chrome.storage.local.set(options, function() {
      // Update status to let user know options were saved.
      var status = document.getElementById('status');
      status.textContent = 'Options saved.';
      setTimeout(function() {
        status.textContent = '';
      }, 750);
    });
  }
  var customs = document.querySelector("#custom-sections");
  // Load personal settings
  chrome.storage.local.get(null, function(conf){

    if(conf.main_batch == undefined)
    {
      console.error("No main batch found, run tridion first");
      return;
    }

    console.log("loading levels");
    ls_main_levels = conf.main_batch.levels;
    ls_main_conf = conf.main_batch.conf;
    ls_cust_batches = conf.cust_batches;

    // Load Main batch
    var all_lvl_container = document.querySelector("option-section");
    var levels = new OptionLevels(ls_main_levels);
    levels.querySelectorAll("input").forEach(function(input) {
      if(ls_main_conf != undefined) input.checked = ls_main_conf[input.value];

      // main level change listeners
      input.addEventListener("change", function () {
        var id =input.id;

        if(this.checked)
          document.querySelectorAll("#custom-sections option-levels").forEach(function(levels){
            var added = false;
            levels.querySelectorAll("input").forEach(function(section_input){
              if(section_input.value > input.value && !added){
                section_input.parentNode.insertAdjacentElement('beforebegin', new OptionLevel({id:input.id, name:input.value}))
                added = true;
              }
            });

            if(!added) levels.appendChild( new OptionLevel({id:input.id, name:input.value}));
          });
        else
          document.querySelectorAll('#custom-sections input[id="'+input.id+'"]').forEach(function(input){
            input.parentNode.remove();
          });
      })
    },this);
    all_lvl_container.appendChild(levels);
    $(all_lvl_container).find("h2").click(function(){$(levels).toggle();});
    $(all_lvl_container).find("h2").click();

    if(ls_cust_batches == undefined){
      console.error("No custom batch found, create one first");
      return;
    }
    // Load custom batches
    var cust_container = document.querySelector("#custom-sections");
    ls_cust_batches.forEach(function(section){
      var cust_section = new OptionsSection(section.name, true, section.id);

      // turn on selected items
      section.conf.forEach(function(level){
        cust_section.querySelector('[id="'+level+'"]').checked = true;
      });

      cust_container.appendChild(cust_section);
    })
  });

  document.querySelector(".add_custom").addEventListener("click", function(){
    customs.appendChild(new OptionsSection("New Batch", true));
  });

  document.querySelector("#save").addEventListener("click", save_options);

})();<commit_msg>Fix no main batch when saving with empty levels<commit_after>
// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
  // Use default value color = 'red' and likesColor = true.
  chrome.storage.sync.get({
    // values to load
  }, function(items) {
    // TODO
  });
}

class OptionLevel extends HTMLElement{
  constructor(level, checked=false){
    super();

    var chkbx = document.createElement("input");
    var span = document.createElement("span");
    chkbx.type = "checkbox";
    chkbx.id = level.id;
    chkbx.value = level.name;
    chkbx.checked = checked;
    span.textContent = level.name;
    this.append(chkbx);
    this.append(span);
    return this;
  }
}
customElements.define('option-level', OptionLevel);

class OptionLevels extends HTMLElement{

  addOption(level){
    this.append(new OptionLevel(level));
  }

  constructor(levels){
    super();
    for(var level in levels)
      this.addOption({name:level, id:levels[level]});
    return this;
  }
}
customElements.define('option-levels', OptionLevels);

class OptionsSection extends HTMLElement{
  constructor(name, custom, id){
    super();

    var title = document.createElement("h2");
    title.textContent = this.getAttribute("name") || name || "Custom batch";
    this.append(title);

    if(custom) {
      this.id = id? id : new Date().valueOf();
      this.custom = true;
      var selected_levels = {};
      document.querySelectorAll("#main-section input:checked").forEach(function(input){
        selected_levels[input.value] = input.id;
      });
      var del = document.createElement("button");
      del.className = "delete";
      del.textContent = "x";
      del.addEventListener("click", function(){this.parentNode.remove();});

      var opt_levels = new OptionLevels(selected_levels);
      $(title).click(function(){ $(opt_levels).toggle();});
      this.appendChild(del);
      this.appendChild(opt_levels);

    }
  }
}
customElements.define('option-section', OptionsSection);

(function(){

  var ls_main_levels;
  var ls_main_conf;
  var ls_cust_batches;

  // Saves options to chrome.storage
  function save_options() {
    console.debug('saving options');

    var options = {
      "main_batch":{
        "levels" : ls_main_levels,
        "conf":{}
      },
      "cust_batches": []
    };

    // Get main_batch changes
    document.querySelectorAll("#main-section input:checked").forEach(function(item){options.main_batch.conf[item.value] = true;});

    // Get custom batches
    document.querySelectorAll("#custom-sections option-section").forEach(function(section){
      var publishable = true;
      var section_conf = {
        "id": section.id,
        "name": section.querySelector("h2").textContent,
        "conf": [],
        "publishable": true
      };
      section.querySelectorAll("input:checked").forEach(function(item){
        if(!item.value.match(/17/) && publishable) publishable = !publishable;
        section_conf.conf.push(item.id);
      });
      section_conf.publishable = publishable;

      options.cust_batches.push(section_conf);
    });

    chrome.storage.local.set(options, function() {
      // Update status to let user know options were saved.
      var status = document.getElementById('status');
      status.textContent = 'Options saved.';
      setTimeout(function() {
        status.textContent = '';
      }, 750);
    });
  }
  var customs = document.querySelector("#custom-sections");
  // Load personal settings
  chrome.storage.local.get(null, function(conf){

    if(conf.main_batch.levels == undefined)
    {
      console.error("No main batch found, run tridion first");
      return;
    }

    console.log("loading levels");
    ls_main_levels = conf.main_batch.levels;
    ls_main_conf = conf.main_batch.conf;
    ls_cust_batches = conf.cust_batches;

    // Load Main batch
    var all_lvl_container = document.querySelector("option-section");
    var levels = new OptionLevels(ls_main_levels);
    levels.querySelectorAll("input").forEach(function(input) {
      if(ls_main_conf != undefined) input.checked = ls_main_conf[input.value];

      // main level change listeners
      input.addEventListener("change", function () {
        var id =input.id;

        if(this.checked)
          document.querySelectorAll("#custom-sections option-levels").forEach(function(levels){
            var added = false;
            levels.querySelectorAll("input").forEach(function(section_input){
              if(section_input.value > input.value && !added){
                section_input.parentNode.insertAdjacentElement('beforebegin', new OptionLevel({id:input.id, name:input.value}))
                added = true;
              }
            });

            if(!added) levels.appendChild( new OptionLevel({id:input.id, name:input.value}));
          });
        else
          document.querySelectorAll('#custom-sections input[id="'+input.id+'"]').forEach(function(input){
            input.parentNode.remove();
          });
      })
    },this);
    all_lvl_container.appendChild(levels);
    $(all_lvl_container).find("h2").click(function(){$(levels).toggle();});
    $(all_lvl_container).find("h2").click();

    if(ls_cust_batches == undefined){
      console.error("No custom batch found, create one first");
      return;
    }
    // Load custom batches
    var cust_container = document.querySelector("#custom-sections");
    ls_cust_batches.forEach(function(section){
      var cust_section = new OptionsSection(section.name, true, section.id);

      // turn on selected items
      section.conf.forEach(function(level){
        cust_section.querySelector('[id="'+level+'"]').checked = true;
      });

      cust_container.appendChild(cust_section);
    })
  });

  document.querySelector(".add_custom").addEventListener("click", function(){
    customs.appendChild(new OptionsSection("New Batch", true));
  });

  document.querySelector("#save").addEventListener("click", save_options);

})();<|endoftext|>


<commit_before>/*
 * Scripts for the web version of punchtime.io
 *
 * @author Haroen Viaene <hello@haroen.me>
 */

var firebase = require('firebase');

(()=>{
  /* jshint ignore:start */
  /* google analytics */
  window.ga = window.ga || function() {
      (ga.q = ga.q||[]).push(arguments);
  };
  ga.l = +new Date;
  // fill in analytics
  ga("create", "", "auto");
  ga("send", "pageview");
  /* jshint ignore:end */
})();

(()=>{
  console.log('test');
})();
<commit_msg>initial addition of firebase<commit_after>/*
 * Scripts for the web version of punchtime.io
 *
 * @author Haroen Viaene <hello@haroen.me>
 */

var Firebase = require('firebase');

var base = new Firebase('https://scorching-inferno-1467.firebaseio.com/');

base.child('0/tier/').on('value', (snapshot) => {
  console.log('tier: '+snapshot.val());
});

// https://www.firebase.com/docs/web/guide/saving-data.html
base.child('0/tier/').set("paid");

(()=>{
  /* jshint ignore:start */
  /* google analytics */
  window.ga = window.ga || function(){
      (ga.q = ga.q||[]).push(arguments);
  };
  ga.l = +new Date;
  // fill in analytics
  ga('create', '', 'auto');
  ga('send', 'pageview');
  /* jshint ignore:end */
})();

(()=>{
  console.log(`%cWelcome to punchtime
All our code is open source, if you want to help, take a look at https://github.com/punchtime`, "font-family: sans-serif; color: red; font-size: 1em;");
})();
<|endoftext|>