<commit_before>// JavaScript Document

// Scripts written by YOURNAME @ YOURCOMPANY

// Inspired by https://medium.com/outsystems-experts/gestures-glamour-setting-up-a-touch-menu-6d9b94039997#.d2r8ubylw

document.addEventListener("DOMContentLoaded", function () {
    const menu_container      = document.querySelector(".navigation-block.-flyout");
    const menu                = document.querySelector(".navigation_inner");
    const overlay             = document.querySelector(".navigation_background");
    const toggle              = document.querySelector(".menu-toggle");
    const active_class        = "is-active";
    const no_transition_class = "_notransition";
    const maxOpacity          = 0.5; // if changed, don't forget to change opacity in css
    const velocity            = 0.3;

    let trackable_element     = false;
    let touching_element      = false;
    let start_time            = 0;
    let start_coords          = [0, 0];
    let current_coords        = [0, 0];

    let is_open               = false;
    let is_moving             = false;
    let menu_width            = 0;
    let last_coords           = [0, 0];
    let move_x                = 0; // where the menu currently
    let drag_direction        = "";

    const close_menu = (translate_x) => {
        function on_transition_end() {
            overlay.style.opacity = "";

            menu_container.removeEventListener("transitionend", on_transition_end, false);
        }

        if (translate_x < 0 || !is_open) {
            menu.style.transform = "";
            menu.style.webkitTransform = "";

            overlay.classList.remove(active_class);

            menu_container.classList.remove(active_class);
            menu_container.addEventListener("transitionend", on_transition_end, false);
        }
    };

    const open_menu = () => {
        menu.style.transform = "";
        menu.style.webkitTransform = "";

        menu_container.classList.add(active_class);

        overlay.classList.add(active_class);
        overlay.style.opacity = "";
    };

    const close_menu_overlay = () => {
        function on_transition_end() {
            overlay.classList.remove(active_class);

            menu_container.removeEventListener("transitionend", on_transition_end);
        }

        menu_container.addEventListener("transitionend", on_transition_end, false);
        menu_container.classList.remove(active_class);
    };

    const click_open_menu = () => {
        overlay.classList.add(active_class);

        requestAnimationFrame(function () {
            setTimeout(function () {
                menu_container.classList.add(active_class);
            }, 1);
        });
    };

    const update_ui = () => {
        if (is_moving) {
            menu.style.transform = "translateX(" + move_x + "px)";
            menu.style.webkitTransform = "translateX(" + move_x + "px)";

            requestAnimationFrame(update_ui);
        }
    };

    const touch_start = (start_coords) => {
        const menu_open = menu_container.classList.contains(active_class);

        if (menu_open !== false) {
            is_open = true;
        } else {
            is_open = false;
        }

        // disable transitions
        menu_container.classList.add(no_transition_class);
        menu.classList.add(no_transition_class);

        is_moving   = true;
        menu_width  = menu.offsetWidth;
        last_coords = start_coords;

        if (is_open) {
            move_x = 0;
        } else {
            move_x = -menu_width;
        }

        drag_direction = "";
        overlay.classList.add(active_class);
    };

    const touch_move = (evt, current_coords, translate_coords) => {
        if (!drag_direction) {
            if (Math.abs(translate_coords[0]) >= Math.abs(translate_coords[1])) {
                drag_direction = "horizontal";
            } else {
                drag_direction = "vertical";
            }

            requestAnimationFrame(update_ui); // this is what effectively does the animation (ﾉ◕ヮ◕)ﾉ*:・ﾟ✧
        }
        if (drag_direction === "vertical") {
            last_coords = current_coords;
        } else {
            evt.preventDefault();

            if (move_x + (current_coords[0] - last_coords[0]) < 0 && move_x + (current_coords[0] - last_coords[0]) > -menu_width) {
                move_x = move_x + (current_coords[0] - last_coords[0]);
            }

            last_coords = current_coords;

            // disable transitions
            overlay.classList.add(no_transition_class);

            const newOpacity = (((maxOpacity) * (100 - ((Math.abs(move_x) * 100) / menu_width))) / 100);

            if (overlay.style.opacity !== newOpacity.toFixed(2) && newOpacity.toFixed(1) % 1 !== 0) {
                overlay.style.opacity = newOpacity.toFixed(2);
            }

        }
    };

    const touch_end = (current_coords, translate_coords, time_taken) => {
        is_moving = false;

        if (current_coords === [0, 0]) {
            if (is_open) {
                // enable transitions
                menu.classList.remove(no_transition_class);
                menu_container.classList.remove(no_transition_class);
            } else {
                // enable transitions
                overlay.classList.remove(active_class);
                menu_container.classList.remove(no_transition_class);
            }
        } else {
            if (is_open) {
                if ((translate_coords[0] < (-menu_width) / 2) || (Math.abs(translate_coords[0]) / time_taken > velocity)) {
                    close_menu(translate_coords[0]);
                    is_open = false;
                } else {
                    open_menu();
                    is_open = true;
                }
            } else {
                if (translate_coords[0] > menu_width / 2 || (Math.abs(translate_coords[0]) / time_taken > velocity)) {
                    open_menu();
                    is_open = true;
                } else {
                    close_menu(translate_coords[0]);
                    is_open = false;
                }

            }
        }

        // enable transitions
        menu_container.classList.remove(no_transition_class);
        menu.classList.remove(no_transition_class);
        overlay.classList.remove(no_transition_class);
    };

    const on_touch_start = (evt) => {
        start_time = new Date().getTime();
        start_coords = [evt.touches[0].pageX, evt.touches[0].pageY];

        touching_element = true;

        touch_start(start_coords);
    };

    const on_touch_move = (evt) => {
        if (!touching_element) {
            return;
        }

        current_coords          = [evt.touches[0].pageX, evt.touches[0].pageY];
        const translate_coords  = [(current_coords[0] - start_coords[0]), (current_coords[1] - start_coords[1])];

        touch_move(evt, current_coords, translate_coords);
    };

    const on_touch_end = () => {
        if (!touching_element) {
            return;
        }

        touching_element = false;

        const translate_coords = [(current_coords[0] - start_coords[0]), (current_coords[1] - start_coords[1])];

        const time_taken = (new Date().getTime() - start_time);

        touch_end(current_coords, translate_coords, time_taken);
    };

    const init = (element) => {
        trackable_element = element;

        start_time = new Date().getTime(); // start time of the touch

        trackable_element.addEventListener("touchstart", on_touch_start, false);
        trackable_element.addEventListener("touchmove", on_touch_move, false);
        trackable_element.addEventListener("touchend", on_touch_end, false);

        overlay.addEventListener("click", close_menu_overlay, false); // click the overlay to immediately close the menu
        toggle.addEventListener("click", click_open_menu, false);     // click the toggle to immediately open the menu
    };

    init(menu);
});
<commit_msg>Focus the mobile menu when it's toggled<commit_after>// JavaScript Document

// Scripts written by YOURNAME @ YOURCOMPANY

// Inspired by https://medium.com/outsystems-experts/gestures-glamour-setting-up-a-touch-menu-6d9b94039997#.d2r8ubylw

document.addEventListener("DOMContentLoaded", function () {
    const menu_container      = document.querySelector(".navigation-block.-flyout");
    const menu                = document.querySelector(".navigation_inner");
    const overlay             = document.querySelector(".navigation_background");
    const toggle              = document.querySelector(".menu-toggle");
    const active_class        = "is-active";
    const no_transition_class = "_notransition";
    const maxOpacity          = 0.5; // if changed, don't forget to change opacity in css
    const velocity            = 0.3;

    let trackable_element     = false;
    let touching_element      = false;
    let start_time            = 0;
    let start_coords          = [0, 0];
    let current_coords        = [0, 0];

    let is_open               = false;
    let is_moving             = false;
    let menu_width            = 0;
    let last_coords           = [0, 0];
    let move_x                = 0; // where the menu currently
    let drag_direction        = "";

    const close_menu = (translate_x) => {
        function on_transition_end() {
            overlay.style.opacity = "";

            menu_container.removeEventListener("transitionend", on_transition_end, false);
        }

        if (translate_x < 0 || !is_open) {
            menu.style.transform = "";
            menu.style.webkitTransform = "";

            overlay.classList.remove(active_class);

            menu_container.classList.remove(active_class);
            menu_container.addEventListener("transitionend", on_transition_end, false);
        }
    };

    const open_menu = () => {
        menu.style.transform = "";
        menu.style.webkitTransform = "";

        menu_container.classList.add(active_class);

        overlay.classList.add(active_class);
        overlay.style.opacity = "";
    };

    const close_menu_overlay = () => {
        function on_transition_end() {
            overlay.classList.remove(active_class);

            menu_container.removeEventListener("transitionend", on_transition_end);
        }

        menu_container.addEventListener("transitionend", on_transition_end, false);
        menu_container.classList.remove(active_class);
    };

    const click_open_menu = () => {
        overlay.classList.add(active_class);

        requestAnimationFrame(function () {
            setTimeout(function () {
                menu_container.classList.add(active_class);
                menu_container.focus();
            }, 1);
        });
    };

    const update_ui = () => {
        if (is_moving) {
            menu.style.transform = "translateX(" + move_x + "px)";
            menu.style.webkitTransform = "translateX(" + move_x + "px)";

            requestAnimationFrame(update_ui);
        }
    };

    const touch_start = (start_coords) => {
        const menu_open = menu_container.classList.contains(active_class);

        if (menu_open !== false) {
            is_open = true;
        } else {
            is_open = false;
        }

        // disable transitions
        menu_container.classList.add(no_transition_class);
        menu.classList.add(no_transition_class);

        is_moving   = true;
        menu_width  = menu.offsetWidth;
        last_coords = start_coords;

        if (is_open) {
            move_x = 0;
        } else {
            move_x = -menu_width;
        }

        drag_direction = "";
        overlay.classList.add(active_class);
    };

    const touch_move = (evt, current_coords, translate_coords) => {
        if (!drag_direction) {
            if (Math.abs(translate_coords[0]) >= Math.abs(translate_coords[1])) {
                drag_direction = "horizontal";
            } else {
                drag_direction = "vertical";
            }

            requestAnimationFrame(update_ui); // this is what effectively does the animation (ﾉ◕ヮ◕)ﾉ*:・ﾟ✧
        }
        if (drag_direction === "vertical") {
            last_coords = current_coords;
        } else {
            evt.preventDefault();

            if (move_x + (current_coords[0] - last_coords[0]) < 0 && move_x + (current_coords[0] - last_coords[0]) > -menu_width) {
                move_x = move_x + (current_coords[0] - last_coords[0]);
            }

            last_coords = current_coords;

            // disable transitions
            overlay.classList.add(no_transition_class);

            const newOpacity = (((maxOpacity) * (100 - ((Math.abs(move_x) * 100) / menu_width))) / 100);

            if (overlay.style.opacity !== newOpacity.toFixed(2) && newOpacity.toFixed(1) % 1 !== 0) {
                overlay.style.opacity = newOpacity.toFixed(2);
            }

        }
    };

    const touch_end = (current_coords, translate_coords, time_taken) => {
        is_moving = false;

        if (current_coords === [0, 0]) {
            if (is_open) {
                // enable transitions
                menu.classList.remove(no_transition_class);
                menu_container.classList.remove(no_transition_class);
            } else {
                // enable transitions
                overlay.classList.remove(active_class);
                menu_container.classList.remove(no_transition_class);
            }
        } else {
            if (is_open) {
                if ((translate_coords[0] < (-menu_width) / 2) || (Math.abs(translate_coords[0]) / time_taken > velocity)) {
                    close_menu(translate_coords[0]);
                    is_open = false;
                } else {
                    open_menu();
                    is_open = true;
                }
            } else {
                if (translate_coords[0] > menu_width / 2 || (Math.abs(translate_coords[0]) / time_taken > velocity)) {
                    open_menu();
                    is_open = true;
                } else {
                    close_menu(translate_coords[0]);
                    is_open = false;
                }

            }
        }

        // enable transitions
        menu_container.classList.remove(no_transition_class);
        menu.classList.remove(no_transition_class);
        overlay.classList.remove(no_transition_class);
    };

    const on_touch_start = (evt) => {
        start_time = new Date().getTime();
        start_coords = [evt.touches[0].pageX, evt.touches[0].pageY];

        touching_element = true;

        touch_start(start_coords);
    };

    const on_touch_move = (evt) => {
        if (!touching_element) {
            return;
        }

        current_coords          = [evt.touches[0].pageX, evt.touches[0].pageY];
        const translate_coords  = [(current_coords[0] - start_coords[0]), (current_coords[1] - start_coords[1])];

        touch_move(evt, current_coords, translate_coords);
    };

    const on_touch_end = () => {
        if (!touching_element) {
            return;
        }

        touching_element = false;

        const translate_coords = [(current_coords[0] - start_coords[0]), (current_coords[1] - start_coords[1])];

        const time_taken = (new Date().getTime() - start_time);

        touch_end(current_coords, translate_coords, time_taken);
    };

    const init = (element) => {
        trackable_element = element;

        start_time = new Date().getTime(); // start time of the touch

        trackable_element.addEventListener("touchstart", on_touch_start, false);
        trackable_element.addEventListener("touchmove", on_touch_move, false);
        trackable_element.addEventListener("touchend", on_touch_end, false);

        overlay.addEventListener("click", close_menu_overlay, false); // click the overlay to immediately close the menu
        toggle.addEventListener("click", click_open_menu, false);     // click the toggle to immediately open the menu
    };

    init(menu);
});
<|endoftext|>


<commit_before>/**
 * Copyright 2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
    "use strict";
    var fs = require('fs');
    var minimatch = require("minimatch");

    function AWSNode(n) {
        RED.nodes.createNode(this,n);
        if (this.credentials &&
            this.credentials.accesskeyid && this.credentials.secretaccesskey) {
            this.AWS = require("aws-sdk");
            this.AWS.config.update({
                accessKeyId: this.credentials.accesskeyid,
                secretAccessKey: this.credentials.secretaccesskey,
            });
        }
    }

    RED.nodes.registerType("aws-config",AWSNode,{
        credentials: {
            accesskeyid: { type:"text" },
            secretaccesskey: { type: "password" }
        }
    });

    function AmazonS3InNode(n) {
        RED.nodes.createNode(this,n);
        this.awsConfig = RED.nodes.getNode(n.aws);
        // eu-west-1||us-east-1||us-west-1||us-west-2||eu-central-1||ap-northeast-1||ap-northeast-2||ap-southeast-1||ap-southeast-2||sa-east-1
        this.region = n.region || "eu-west-1";
        this.bucket = n.bucket;
        this.filepattern = n.filepattern || "";
        var node = this;
        var AWS = this.awsConfig ? this.awsConfig.AWS : null;

        if (!AWS) {
            node.warn(RED._("aws.warn.missing-credentials"));
            return;
        }
        var s3 = new AWS.S3({"region": node.region});
        node.status({fill:"blue",shape:"dot",text:"aws.status.initializing"});
        s3.listObjects({ Bucket: node.bucket }, function(err, data) {
            if (err) {
                node.error(RED._("aws.error.failed-to-fetch", {err:err}));
                node.status({fill:"red",shape:"ring",text:"aws.status.error"});
                return;
            }
            var contents = node.filterContents(data.Contents);
            node.state = contents.map(function (e) { return e.Key; });
            node.status({});
            node.on("input", function(msg) {
                node.status({fill:"blue",shape:"dot",text:"aws.status.checking-for-changes"});
                s3.listObjects({ Bucket: node.bucket }, function(err, data) {
                    if (err) {
                        node.error(RED._("aws.error.failed-to-fetch", {err:err}),msg);
                        node.status({});
                        return;
                    }
                    node.status({});
                    var newContents = node.filterContents(data.Contents);
                    var seen = {};
                    var i;
                    msg.bucket = node.bucket;
                    for (i = 0; i < node.state.length; i++) {
                        seen[node.state[i]] = true;
                    }
                    for (i = 0; i < newContents.length; i++) {
                        var file = newContents[i].Key;
                        if (seen[file]) {
                            delete seen[file];
                        } else {
                            msg.payload = file;
                            msg.file = file.substring(file.lastIndexOf('/')+1);
                            msg.event = 'add';
                            msg.data = newContents[i];
                            node.send(msg);
                        }
                    }
                    for (var f in seen) {
                        if (seen.hasOwnProperty(f)) {
                            msg.payload = f;
                            msg.file = f.substring(f.lastIndexOf('/')+1);
                            msg.event = 'delete';
                            // msg.data intentionally null
                            node.send(msg);
                        }
                    }
                    node.state = newContents.map(function (e) {return e.Key;});
                });
            });
            var interval = setInterval(function() {
                node.emit("input", {});
            }, 900000); // 15 minutes
            node.on("close", function() {
                if (interval !== null) {
                    clearInterval(interval);
                }
            });
        });
    }
    RED.nodes.registerType("amazon s3 in", AmazonS3InNode);

    AmazonS3InNode.prototype.filterContents = function(contents) {
        var node = this;
        return node.filepattern ? contents.filter(function (e) {
            return minimatch(e.Key, node.filepattern);
        }) : contents;
    };

    function AmazonS3QueryNode(n) {
        RED.nodes.createNode(this,n);
        this.awsConfig = RED.nodes.getNode(n.aws);
        this.region = n.region || "eu-west-1";
        this.bucket = n.bucket;
        this.filename = n.filename || "";
        var node = this;
        var AWS = this.awsConfig ? this.awsConfig.AWS : null;

        if (!AWS) {
            node.warn(RED._("aws.warn.missing-credentials"));
            return;
        }
        var s3 = new AWS.S3({"region": node.region});
        node.on("input", function(msg) {
            var bucket = node.bucket || msg.bucket;
            if (bucket === "") {
                node.error(RED._("aws.error.no-bucket-specified"),msg);
                return;
            }
            var filename = node.filename || msg.filename;
            if (filename === "") {
                node.warn("No filename");
                node.error(RED._("aws.error.no-filename-specified"),msg);
                return;
            }
            msg.bucket = bucket;
            msg.filename = filename;
            node.status({fill:"blue",shape:"dot",text:"aws.status.downloading"});
            s3.getObject({
                Bucket: bucket,
                Key: filename,
            }, function(err, data) {
                if (err) {
                    node.warn(err);
                    node.error(RED._("aws.error.download-failed",{err:err.toString()}),msg);
                    return;
                } else {
                    msg.payload = data.Body;
                }
                node.status({});
                node.send(msg);
            });
        });
    }
    RED.nodes.registerType("amazon s3", AmazonS3QueryNode);

    function AmazonS3OutNode(n) {
        RED.nodes.createNode(this,n);
        this.awsConfig = RED.nodes.getNode(n.aws);
        this.region = n.region  || "eu-west-1";
        this.bucket = n.bucket;
        this.filename = n.filename || "";
        this.localFilename = n.localFilename || "";
        var node = this;
        var AWS = this.awsConfig ? this.awsConfig.AWS : null;

        if (!AWS) {
            node.warn(RED._("aws.warn.missing-credentials"));
            return;
        }
        if (AWS) {
            var s3 = new AWS.S3({"region": node.region});
            node.status({fill:"blue",shape:"dot",text:"aws.status.checking-credentials"});
            s3.listObjects({ Bucket: node.bucket }, function(err) {
                if (err) {
                    node.warn(err);
                    node.error(RED._("aws.error.aws-s3-error",{err:err}));
                    node.status({fill:"red",shape:"ring",text:"aws.status.error"});
                    return;
                }
                node.status({});
                node.on("input", function(msg) {
                    var bucket = node.bucket || msg.bucket;
                    if (bucket === "") {
                        node.error(RED._("aws.error.no-bucket-specified"),msg);
                        return;
                    }
                    var filename = node.filename || msg.filename;
                    if (filename === "") {
                        node.error(RED._("aws.error.no-filename-specified"),msg);
                        return;
                    }
                    var localFilename = node.localFilename || msg.localFilename;
                    if (localFilename) {
                        // TODO: use chunked upload for large files
                        node.status({fill:"blue",shape:"dot",text:"aws.status.uploading"});
                        var stream = fs.createReadStream(localFilename);
                        s3.putObject({
                            Bucket: bucket,
                            Body: stream,
                            Key: filename,
                        }, function(err) {
                            if (err) {
                                node.error(err.toString(),msg);
                                node.status({fill:"red",shape:"ring",text:"aws.status.failed"});
                                return;
                            }
                            node.status({});
                        });
                    } else if (typeof msg.payload !== "undefined") {
                        node.status({fill:"blue",shape:"dot",text:"aws.status.uploading"});
                        s3.putObject({
                            Bucket: bucket,
                            Body: RED.util.ensureBuffer(msg.payload),
                            Key: filename,
                        }, function(err) {
                            if (err) {
                                node.error(err.toString(),msg);
                                node.status({fill:"red",shape:"ring",text:"aws.status.failed"});
                                return;
                            }
                            node.status({});
                        });
                    }
                });
            });
        }
    }
    RED.nodes.registerType("amazon s3 out",AmazonS3OutNode);
};
<commit_msg>added ability to loop through bucket (#270)<commit_after>/**
 * Copyright 2014 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

module.exports = function(RED) {
    "use strict";
    var fs = require('fs');
    var minimatch = require("minimatch");

    function AWSNode(n) {
        RED.nodes.createNode(this,n);
        if (this.credentials &&
            this.credentials.accesskeyid && this.credentials.secretaccesskey) {
            this.AWS = require("aws-sdk");
            this.AWS.config.update({
                accessKeyId: this.credentials.accesskeyid,
                secretAccessKey: this.credentials.secretaccesskey,
            });
        }
    }

    RED.nodes.registerType("aws-config",AWSNode,{
        credentials: {
            accesskeyid: { type:"text" },
            secretaccesskey: { type: "password" }
        }
    });

    function AmazonS3InNode(n) {
        RED.nodes.createNode(this,n);
        this.awsConfig = RED.nodes.getNode(n.aws);
        // eu-west-1||us-east-1||us-west-1||us-west-2||eu-central-1||ap-northeast-1||ap-northeast-2||ap-southeast-1||ap-southeast-2||sa-east-1
        this.region = n.region || "eu-west-1";
        this.bucket = n.bucket;
        this.filepattern = n.filepattern || "";
        var node = this;
        var AWS = this.awsConfig ? this.awsConfig.AWS : null;

        if (!AWS) {
            node.warn(RED._("aws.warn.missing-credentials"));
            return;
        }
        var s3 = new AWS.S3({"region": node.region});
        node.status({fill:"blue",shape:"dot",text:"aws.status.initializing"});
        var contents = [];
        node.listAllObjects(s3, { Bucket: node.bucket },contents, function(err, data) {
            if (err) {
                node.error(RED._("aws.error.failed-to-fetch", {err:err}));
                node.status({fill:"red",shape:"ring",text:"aws.status.error"});
                return;
            }
            var contents = node.filterContents(data);
            node.state = contents.map(function (e) { return e.Key; });
            node.status({});
            node.on("input", function(msg) {
                node.status({fill:"blue",shape:"dot",text:"aws.status.checking-for-changes"});
                var contents = [];
                node.listAllObjects(s3, { Bucket: node.bucket }, contents, function(err, data) {
                    if (err) {
                        node.error(RED._("aws.error.failed-to-fetch", {err:err}),msg);
                        node.status({});
                        return;
                    }
                    node.status({});
                    var newContents = node.filterContents(data);
                    var seen = {};
                    var i;
                    msg.bucket = node.bucket;
                    for (i = 0; i < node.state.length; i++) {
                        seen[node.state[i]] = true;
                    }
                    for (i = 0; i < newContents.length; i++) {
                        var file = newContents[i].Key;
                        if (seen[file]) {
                            delete seen[file];
                        } else {
                            msg.payload = file;
                            msg.file = file.substring(file.lastIndexOf('/')+1);
                            msg.event = 'add';
                            msg.data = newContents[i];
                            node.send(msg);
                        }
                    }
                    for (var f in seen) {
                        if (seen.hasOwnProperty(f)) {
                            msg.payload = f;
                            msg.file = f.substring(f.lastIndexOf('/')+1);
                            msg.event = 'delete';
                            // msg.data intentionally null
                            node.send(msg);
                        }
                    }
                    node.state = newContents.map(function (e) {return e.Key;});
                });
            });
            var interval = setInterval(function() {
                node.emit("input", {});
            }, 900000); // 15 minutes
            node.on("close", function() {
                if (interval !== null) {
                    clearInterval(interval);
                }
            });
        });
    }
    RED.nodes.registerType("amazon s3 in", AmazonS3InNode);

    AmazonS3InNode.prototype.listAllObjects = function(s3, params, contents, cb) {
        var node = this;
        s3.listObjects(params, function(err, data) {
            if (err) {
                cb(err, contents);
            } else {
                contents = contents.concat(data.Contents);
                if (data.IsTruncated) {
                    // Set Marker to last returned key
                    params.Marker = contents[contents.length-1].Key;
                    node.listAllObjects(s3, params, contents, cb);
                } else {
                    cb(err, contents);
                }
            }
        });
    };

    AmazonS3InNode.prototype.filterContents = function(contents) {
        var node = this;
        return node.filepattern ? contents.filter(function (e) {
            return minimatch(e.Key, node.filepattern);
        }) : contents;
    };

    function AmazonS3QueryNode(n) {
        RED.nodes.createNode(this,n);
        this.awsConfig = RED.nodes.getNode(n.aws);
        this.region = n.region || "eu-west-1";
        this.bucket = n.bucket;
        this.filename = n.filename || "";
        var node = this;
        var AWS = this.awsConfig ? this.awsConfig.AWS : null;

        if (!AWS) {
            node.warn(RED._("aws.warn.missing-credentials"));
            return;
        }
        var s3 = new AWS.S3({"region": node.region});
        node.on("input", function(msg) {
            var bucket = node.bucket || msg.bucket;
            if (bucket === "") {
                node.error(RED._("aws.error.no-bucket-specified"),msg);
                return;
            }
            var filename = node.filename || msg.filename;
            if (filename === "") {
                node.warn("No filename");
                node.error(RED._("aws.error.no-filename-specified"),msg);
                return;
            }
            msg.bucket = bucket;
            msg.filename = filename;
            node.status({fill:"blue",shape:"dot",text:"aws.status.downloading"});
            s3.getObject({
                Bucket: bucket,
                Key: filename,
            }, function(err, data) {
                if (err) {
                    node.warn(err);
                    node.error(RED._("aws.error.download-failed",{err:err.toString()}),msg);
                    return;
                } else {
                    msg.payload = data.Body;
                }
                node.status({});
                node.send(msg);
            });
        });
    }
    RED.nodes.registerType("amazon s3", AmazonS3QueryNode);

    function AmazonS3OutNode(n) {
        RED.nodes.createNode(this,n);
        this.awsConfig = RED.nodes.getNode(n.aws);
        this.region = n.region  || "eu-west-1";
        this.bucket = n.bucket;
        this.filename = n.filename || "";
        this.localFilename = n.localFilename || "";
        var node = this;
        var AWS = this.awsConfig ? this.awsConfig.AWS : null;

        if (!AWS) {
            node.warn(RED._("aws.warn.missing-credentials"));
            return;
        }
        if (AWS) {
            var s3 = new AWS.S3({"region": node.region});
            node.status({fill:"blue",shape:"dot",text:"aws.status.checking-credentials"});
            s3.listObjects({ Bucket: node.bucket }, function(err) {
                if (err) {
                    node.warn(err);
                    node.error(RED._("aws.error.aws-s3-error",{err:err}));
                    node.status({fill:"red",shape:"ring",text:"aws.status.error"});
                    return;
                }
                node.status({});
                node.on("input", function(msg) {
                    var bucket = node.bucket || msg.bucket;
                    if (bucket === "") {
                        node.error(RED._("aws.error.no-bucket-specified"),msg);
                        return;
                    }
                    var filename = node.filename || msg.filename;
                    if (filename === "") {
                        node.error(RED._("aws.error.no-filename-specified"),msg);
                        return;
                    }
                    var localFilename = node.localFilename || msg.localFilename;
                    if (localFilename) {
                        // TODO: use chunked upload for large files
                        node.status({fill:"blue",shape:"dot",text:"aws.status.uploading"});
                        var stream = fs.createReadStream(localFilename);
                        s3.putObject({
                            Bucket: bucket,
                            Body: stream,
                            Key: filename,
                        }, function(err) {
                            if (err) {
                                node.error(err.toString(),msg);
                                node.status({fill:"red",shape:"ring",text:"aws.status.failed"});
                                return;
                            }
                            node.status({});
                        });
                    } else if (typeof msg.payload !== "undefined") {
                        node.status({fill:"blue",shape:"dot",text:"aws.status.uploading"});
                        s3.putObject({
                            Bucket: bucket,
                            Body: RED.util.ensureBuffer(msg.payload),
                            Key: filename,
                        }, function(err) {
                            if (err) {
                                node.error(err.toString(),msg);
                                node.status({fill:"red",shape:"ring",text:"aws.status.failed"});
                                return;
                            }
                            node.status({});
                        });
                    }
                });
            });
        }
    }
    RED.nodes.registerType("amazon s3 out",AmazonS3OutNode);
};
<|endoftext|>


<commit_before>"use strict";
var React = require("react/addons"),
    deepEqual = require("deep-equal"),

    expose_getters_from = require("./helpers/expose_getters_from"),
    to_event_map = require("./helpers/to_event_map"),
    assign_event_map_to_prop_types_and_spec = require("./helpers/assign_event_map_to_prop_types_and_spec"),
    EventBindingMixin = require("./mixins/EventBindingMixin"),

    EVENT_NAMES = "bounds_changed center_changed click dblclick drag dragend dragstart heading_changed idle maptypeid_changed mousemove mouseout mouseover projection_changed resize rightclick tilesloaded tilt_changed zoom_changed",
    EVENT_MAP = to_event_map(EVENT_NAMES),

    MapSpec,
    MapPropTypes;

function ensure_map_created (component, createdCallback, createFactory) {
  var $__0=  component,context=$__0.context,
      map = context.getMap(),
      noMap = !map;

  if (noMap && context.getApi() && createFactory) {
    map = createFactory(component, context);
  }
  if (map) {
    createdCallback(map);
    if (noMap) {
      component.setState({_initialized: true});
    }
  }
}

function create_map (component, context) {
  var $__0=  context.getApi(),Map=$__0.Map,
      map = new Map(
    component.refs.mapCanvas.getDOMNode(),
    component.props
  );
  expose_getters_from(component, Map.prototype, map);
  return context._set_map(map);
}

MapPropTypes = {

};

MapSpec = {
  displayName: "Map",

  mixins: [EventBindingMixin(EVENT_MAP)],

  propTypes: MapPropTypes,

  contextTypes: {
    getMap: React.PropTypes.func,
    getApi: React.PropTypes.func,
    hasMap: React.PropTypes.func,
    _set_map: React.PropTypes.func
  },

  /*
   * Some public API we'd like to expose
   */
  panBy:function (x, y) {
    ensure_map_created(this, function(map)  {
      map.panBy(x, y);
    });
  },

  panTo:function (latLng) {
    ensure_map_created(this, function(map)  {
      map.panTo(latLng);
    });
  },

  panToBounds:function (latLngBounds) {
    ensure_map_created(this, function(map)  {
      map.panToBounds(latLngBounds);
    });
  },

  getInitialState:function () {
    return {
      /* [null, false, true] => ["init", "api loaded", "done"] */
      _initialized: null,
    };
  },

  shouldComponentUpdate:function(nextProps, nextState) {
    return !deepEqual(nextProps, this.props) || nextState._initialized !== this.state._initialized;
  },

  componentDidMount:function () {
    ensure_map_created(this, this.add_listeners, create_map);
  },

  componentWillReceiveProps:function (nextProps, nextContext) {
    if (null == this.state._initialized && nextContext.getApi()) {
      this.setState({
        _initialized: false,
      });
    }
  },

  componentDidUpdate:function () {
    ensure_map_created(this, function(map)  {
      map.setOptions(this.props);
      this.add_listeners(map);
    }.bind(this), create_map);
  },

  componentWillUnmount:function () {
    ensure_map_created(this, function(map)  {
      this.clear_listeners(map);
      this.context._set_map(null);
    }.bind(this));
  },

  render:function () {
    return this._render(this.props, this.state);
  },

  _render:function (props, state) {
    return React.createElement("div", React.__spread({ref: "mapCanvas"},  props));
  }
};

assign_event_map_to_prop_types_and_spec(EVENT_MAP, MapPropTypes, MapSpec);

module.exports = React.createClass(MapSpec);
<commit_msg>chore(lib): compile harmony jsx to js<commit_after>"use strict";
var React = require("react/addons"),
    deepEqual = require("deep-equal"),

    expose_getters_from = require("./helpers/expose_getters_from"),
    to_event_map = require("./helpers/to_event_map"),
    assign_event_map_to_prop_types_and_spec = require("./helpers/assign_event_map_to_prop_types_and_spec"),
    EventBindingMixin = require("./mixins/EventBindingMixin"),

    EVENT_NAMES = "bounds_changed center_changed click dblclick drag dragend dragstart heading_changed idle maptypeid_changed mousemove mouseout mouseover projection_changed resize rightclick tilesloaded tilt_changed zoom_changed",
    EVENT_MAP = to_event_map(EVENT_NAMES),

    MapSpec,
    MapPropTypes;

function ensure_map_created (component, createdCallback, createFactory) {
  var $__0=  component,context=$__0.context,
      map = context.getMap(),
      noMap = !map;

  if (noMap && context.getApi() && createFactory) {
    map = createFactory(component, context);
  }
  if (map) {
    createdCallback(map);
    if (noMap) {
      component.setState({_initialized: true});
    }
  }
}

function create_map (component, context) {
  var $__0=  context.getApi(),Map=$__0.Map,
      map = new Map(
    component.refs.mapCanvas.getDOMNode(),
    component.props
  );
  expose_getters_from(component, Map.prototype, map);
  return context._set_map(map);
}

MapPropTypes = {

};

MapSpec = {
  displayName: "Map",

  mixins: [EventBindingMixin(EVENT_MAP)],

  propTypes: MapPropTypes,

  contextTypes: {
    getMap: React.PropTypes.func,
    getApi: React.PropTypes.func,
    hasMap: React.PropTypes.func,
    _set_map: React.PropTypes.func
  },

  /*
   * Some public API we'd like to expose
   */
  panBy:function (x, y) {
    ensure_map_created(this, function(map)  {
      map.panBy(x, y);
    });
  },

  panTo:function (latLng) {
    ensure_map_created(this, function(map)  {
      map.panTo(latLng);
    });
  },

  panToBounds:function (latLngBounds) {
    ensure_map_created(this, function(map)  {
      map.panToBounds(latLngBounds);
    });
  },

  getInitialState:function () {
    return {
      /* [null, false, true] => ["init", "api loaded", "done"] */
      _initialized: null,
    };
  },

  shouldComponentUpdate:function(nextProps, nextState) {
    return !deepEqual(nextProps, this.props) || nextState._initialized !== this.state._initialized;
  },

  componentDidMount:function () {
    ensure_map_created(this, this.add_listeners, create_map);
  },

  componentWillReceiveProps:function (nextProps, nextContext) {
    if (null == this.state._initialized && nextContext.getApi()) {
      this.setState({
        _initialized: false,
      });
    }
  },

  componentDidUpdate:function () {
    ensure_map_created(this, function(map)  {
      map.setOptions(this.props);
      this.add_listeners(map);
    }.bind(this), create_map);
  },

  componentWillUnmount:function () {
    ensure_map_created(this, function(map)  {
      this.clear_listeners(map);
      this.context._set_map(null);
    }.bind(this));
  },

  render:function () {
    return this._render(this.props, this.state);
  },

  _render:function (props, state) {
    return React.createElement("div", {ref: "mapCanvas",
      id: props.id, className: props.className, style: props.style}
    );
  }
};

assign_event_map_to_prop_types_and_spec(EVENT_MAP, MapPropTypes, MapSpec);

module.exports = React.createClass(MapSpec);
<|endoftext|>


<commit_before>
var React = require('react/addons'),
	logger = require('./logging').getLogger(__LOGGER__),
	RequestContext = require('./context/RequestContext'),
	RequestLocalStorage = require('./util/RequestLocalStorage'),
	Q = require('q'),
	cssHelper = require('./util/ClientCssHelper'),
	EventEmitter = require("events").EventEmitter,
	ClientRequest = require("./ClientRequest"),
	History = require('./components/History'),
	PageUtil = require("./util/PageUtil"),
	TritonAgent = require('./util/TritonAgent');

// for dev tools
window.React = React;

var TRITON_DATA_ATTRIBUTE = "data-triton-root-id";

class ClientController extends EventEmitter {

	constructor ({routes}) {
		var dehydratedState = window.__tritonState;

		checkNotEmpty(dehydratedState, 'InitialContext');
		checkNotEmpty(dehydratedState, 'Config');

		RequestLocalStorage.startRequest();

		this.config = buildConfig(dehydratedState.Config);

		if (routes.onClientConfigLoaded) {
			routes.onClientConfigLoaded.call(this);
		}

		this.context = buildContext(
			dehydratedState.InitialContext,
			routes
		);
		this.mountNode = document.getElementById('content');

		var irDfd = this._initialRenderDfd = Q.defer();
		this.once('render', irDfd.resolve.bind(irDfd));

		this._setupNavigateListener();
		this._setupLateArrivalHandler();

		this._previouslyRendered = false;
	}

	terminate() {
        this._history.off(this._historyListener);
        this._historyListener = null;
        this._history = null;
	}

	_startRequest() {

		// If this is a secondary request (client transition) within a
		// session, then we'll get a fresh RequestLocalStorage
		// container.
		if (this._previouslyRendered){
			RequestLocalStorage.startRequest();
			// we need to re-register the request context as a RequestLocal.
			this.context.registerRequestLocal();
		}
	}

	_setupNavigateListener () {
		var context = this.context;

		context.onNavigateStart(this._startRequest.bind(this));

		/**
		 * type is one of
		 *    History.events.PUSHSTATE: user clicked something to go forward but browser didn't do a
		 * full page load
		 *    History.events.POPSTATE: user clicked back button but browser didn't do a full page load
		 *    History.events.PAGELOAD: full browser page load, not using History API.
		 */
		context.onNavigate( (err, page, path, type) => {
			logger.debug('Executing navigate action');

			// if this is a History.events.PUSHSTATE navigation, we should change the URL in the bar location bar
			// before rendering.
			// note that for browsers that do not have pushState, this will result in a window.location change
			// and full browser load. It's kind of late to do that, as we may have waited for handleRoute to
			// finish asynchronously. perhaps we should have an "URLChanged" event that happens before "NavigateDone".
			if (type === History.events.PUSHSTATE && this._history) {
				this._history.pushState(null, null, path);
			}

			if (err) {
				// redirects are sent as errors, so let's handle it if that's the case.
				if (err.status && (err.status === 301 || err.status === 302)) {
					if (!err.redirectUrl) {
						console.error("A redirect status was sent without a corresponding redirect redirectUrl.", err);
					} else {
						setTimeout(() => {
							this._history.replaceState(null, null, err.redirectUrl);
							this.context.navigate(new ClientRequest(err.redirectUrl));
						}, 0);
					}
				} else {
					logger.error("onNavigate error", err);
				}
				return;
			}

			var routeName = context.navigator.getCurrentRoute().name;

			if (!this._previouslyRendered) {
				cssHelper.registerPageLoad(routeName);
			} else {
				// render the document title.
				this._renderTitle(page);

				// render the base tag.
				this._renderBase(page);

				this._renderMetaTags(page);

			}

			cssHelper.ensureCss(routeName, page);

			page.getBodyClasses().then((classes) => {
				classes.push(`route-${routeName}`);
				document.body.className = classes.join(' ');
			}).then(() => this._render(page)).catch((err) => {
				logger.error("Error while rendering", err.stack);
			});

		});

	}

	_renderTitle(page) {
		page.getTitle().then(newTitle => {
			if (newTitle && newTitle !== document.title) {
				document.title = newTitle;
			}
		}).catch(err => console.error("Error while setting the document title", err));
	}

	_renderBase(page) {
		page.getBase().then(base => {
			var currentBaseTag = document.head.querySelector("head base");
			if (base === null) {
				// get rid of the current base tag.
				if (currentBaseTag) currentBaseTag.parentNode.removeChild(currentBaseTag);
			} else {
				// we need a base tag. add one if it's not there yet.
				if (!currentBaseTag) {
					currentBaseTag = document.createElement("base");
					document.head.appendChild(currentBaseTag);
				}
				currentBaseTag.href = base.href;
				if (base.target) currentBaseTag.target = base.target;
			}

		});
	}

	_renderMetaTags(page) {
		// first, remove all the current meta tags.
		var currentMetaTags = document.head.querySelectorAll("meta");
		for (var i = 0; i < currentMetaTags.length; i++) {
			currentMetaTags[i].parentNode.removeChild(currentMetaTags[i]);
		}

		// now add all the meta tags for the new page.
		page.getMetaTags().forEach((metaTagPromise) => {
			metaTagPromise.then((metaTag) => {
				var parent = document.head;
				if (metaTag.noscript) {
					var noscript = document.createElement("noscript");
					parent.appendChild(noscript);
					parent = noscript;
				}

				var meta = document.createElement("meta");
				["name", "httpEquiv", "charset", "content"].forEach((name) => {
					if (metaTag[name]) meta[name] = metaTag[name];
				});

				parent.appendChild(meta);
			});
		});
	}

	_render (page) {
		var t0 = new Date;
		logger.debug('React Rendering');

		// if we were previously rendered on the client, clean up the old divs and
		// their ReactComponents.
		this._cleanupPreviousRender(this.mountNode);

		// if we are reattaching to server-generated HTML, we should find the root elements that were sent down.
		// we store them in a temp array indexed by their triton-root-id.
		var serverRenderedRoots = [];
		this._getRootElements(this.mountNode).forEach((rootElement) => {
			serverRenderedRoots[rootElement.getAttribute(TRITON_DATA_ATTRIBUTE)] = rootElement;
		});

		var elementPromises = PageUtil.standardizeElements(page.getElements());

		var newRenderedElements = [];
		var renderElement = (element, index) => {
			// if someone already rendered this element, let's not!
			if (newRenderedElements[index]) return;

			// for each ReactElement that we want to render, either use the server-rendered root element, or
			// create a new root element.
			logger.debug("Rendering root node #" + index);
			var root = serverRenderedRoots[index] || this._createTritonRootNode(this.mountNode, index);

			// TODO: get rid of context once continuation-local-storage holds our important context vars.
			element = React.addons.cloneWithProps(element, { context: this.context });
			newRenderedElements[index] = React.render(element, root);
			if (index === elementPromises.length - 1) {
				logger.debug('React Rendered');
				logger.time('render', new Date - t0);
				this.emit("render");
			}
		};

		// if and when the loader runs out of cache, we should render everything we have synchronously through EarlyPromises.
		// this lets us render when the server timed out.
		// TODO: should we ONLY do this when the server times out?
		TritonAgent.cache().whenCacheDepleted().then(() => {
			logger.debug("Loader cache depleted; rendering elements.");
			elementPromises.forEach((elementEarlyPromise, index) => {
				renderElement(elementEarlyPromise.getValue(), index);
			});
		});

		// I find the control flow for chaining promises impossibly mind-bending, but what I intended was something
		// like:
		//
		//		elementPromises.forEach((elementPromise, index) => {
		//			var element = await elementPromise;
		//			renderElement(element, index);
		//		});
		//		this._previouslyRendered = true;
		//		this.emit("render");
		//
		// if it was using ES7 async/await syntax. I learned how to chain an array of promises in ES5/6
		// from http://bahmutov.calepin.co/chaining-promises.html , and apparently it looks like the code below. But
		// if I got something wrong, what I intended was the control flow expressed in this comment.
		return elementPromises.reduce((chain, next, index) => {
			return chain.then((element) => {
		 		renderElement(element, index - 1);
				return next;
			})
		}).then((element) => {
			// reduce is called length - 1 times. we need to call one final time here to make sure we
			// chain the final promise.
	 		renderElement(element, elementPromises.length - 1);

			this._previouslyRendered = true;
		}).catch((err) => {
			logger.error("Error while rendering.", err.stack);
		});
	}

	/**
	 * Cleans up a previous React render in the document. Unmounts all the components and destoys the mounting
	 * DOM node(s) that were created.
	 */
	_cleanupPreviousRender(mountNode) {
		if (this._previouslyRendered) {
			logger.debug("Removing previous page's React components");

			this._getRootElements(mountNode).forEach((tritonRoot) => {
				// since this node has a "data-triton-root-id" attribute, we can assume that we created it and
				// should destroy it. Destruction means first unmounting from React and then destroying the DOM node.
				React.unmountComponentAtNode(tritonRoot);
				mountNode.removeChild(tritonRoot);
			});
		}
	}

	/**
	 * Returns an array of all of the root elements that are children of mountNode. The root elements
	 * should all have a "data-triton-root-id" attribute and be direct children of mountNode.
	 */
	_getRootElements(mountNode) {
		// if children returned an array instead of a NodeList, we could use .filter(), but
		// alas, it does not. We could copy it over to an array, but seems easier to just iterate
		// over the NodeList.
		var potentialRoots = mountNode.children;
		var result = [];
		for (var i = 0; i < potentialRoots.length; i++) {
			var potentialRoot = potentialRoots[i];
			if (potentialRoot.hasAttribute(TRITON_DATA_ATTRIBUTE)) {
				// since this node has a "data-triton-root-id" attribute, we can assume that we created it.
				result.push(potentialRoot);
			} else {
				// it's deeply troubling that there's a div we didn't create, but for now, just warn, and
				// don't obliterate the node.
				console.warn("Found an element inside Triton's rendering canvas that did not have data-triton-root-id " +
					"and was probably not created by Triton. Other code may be manually mucking with the DOM, which could " +
					"cause unpredictable behavior", potentialRoot);
			}
		}
		return result;
	}

	/**
	 * This method creates a new div to render a ReactElement in to at the end of the mount node.
	 */
	_createTritonRootNode(mountNode, index) {
		var root = document.createElement("div");
		root.setAttribute(TRITON_DATA_ATTRIBUTE, index);
		mountNode.appendChild(root);
		return root;
	}

	init () {

		var unloadHandler = () => {this.terminate();};

		if (window && window.addEventListener) {
		    window.addEventListener("unload", unloadHandler);
		}
		else if (window && window.attachEvent) {
		    window.attachEvent("onunload", unloadHandler);
		}

		var location = window.location;
		var path = location.pathname + location.search;
		this._initializeHistoryListener(this.context);
		this.context.navigate(new ClientRequest(path));
	}

	/**
	 * Initializes us to listen to back button events. When the user presses the back button, the history
	 * listener will be called and cause a navigate() event.
	 */
	_initializeHistoryListener(context) {

        this._historyListener = (e) => {
            if (context) {
                var path = this._history.getPath();

                // pass in "popstate" because this is when a user clicks the back button.
                context.navigate(new ClientRequest(path), History.events.POPSTATE);

            }
        };

        this._history = new History();
        this._history.on(this._historyListener);
	}

	_setupLateArrivalHandler () {
		// used by <script> callbacks to register data sent down on the
		// initial connection after initial render
		window.__lateArrival = this.lateArrival.bind(this);
	}

	lateArrival (url, resString) {
		this._initialRenderDfd.promise.done( () => {
			TritonAgent.cache().lateArrival(url, JSON.parse(decodeURIComponent(resString)));
		});
	}

}

function checkNotEmpty(state, key) {
	if (typeof state[key] === 'undefined') {
		var msg = key + ' not defined in dehydrated state';
		logger.error(msg)
		throw new Error(msg);
	}
}

function buildConfig(dehydratedConfig) {
	// rehydrate the config object
	var config = require("./config")();
	config.rehydrate(dehydratedConfig);
	return config;
}

function buildContext(dehydratedContext, routes) {
	var context = new RequestContext.Builder()
		.setRoutes(routes)
		.create();
	context.rehydrate(dehydratedContext);
	return context;
}

module.exports = ClientController;

<commit_msg>Totally disable triton history listener<commit_after>
var React = require('react/addons'),
	logger = require('./logging').getLogger(__LOGGER__),
	RequestContext = require('./context/RequestContext'),
	RequestLocalStorage = require('./util/RequestLocalStorage'),
	Q = require('q'),
	cssHelper = require('./util/ClientCssHelper'),
	EventEmitter = require("events").EventEmitter,
	ClientRequest = require("./ClientRequest"),
	History = require('./components/History'),
	PageUtil = require("./util/PageUtil"),
	TritonAgent = require('./util/TritonAgent');

// for dev tools
window.React = React;

var TRITON_DATA_ATTRIBUTE = "data-triton-root-id";

class ClientController extends EventEmitter {

	constructor ({routes}) {
		var dehydratedState = window.__tritonState;

		checkNotEmpty(dehydratedState, 'InitialContext');
		checkNotEmpty(dehydratedState, 'Config');

		RequestLocalStorage.startRequest();

		this.config = buildConfig(dehydratedState.Config);

		if (routes.onClientConfigLoaded) {
			routes.onClientConfigLoaded.call(this);
		}

		this.context = buildContext(
			dehydratedState.InitialContext,
			routes
		);
		this.mountNode = document.getElementById('content');

		var irDfd = this._initialRenderDfd = Q.defer();
		this.once('render', irDfd.resolve.bind(irDfd));

		this._setupNavigateListener();
		this._setupLateArrivalHandler();

		this._previouslyRendered = false;
	}

	terminate() {
        this._history.off(this._historyListener);
        this._historyListener = null;
        this._history = null;
	}

	_startRequest() {

		// If this is a secondary request (client transition) within a
		// session, then we'll get a fresh RequestLocalStorage
		// container.
		if (this._previouslyRendered){
			RequestLocalStorage.startRequest();
			// we need to re-register the request context as a RequestLocal.
			this.context.registerRequestLocal();
		}
	}

	_setupNavigateListener () {
		var context = this.context;

		context.onNavigateStart(this._startRequest.bind(this));

		/**
		 * type is one of
		 *    History.events.PUSHSTATE: user clicked something to go forward but browser didn't do a
		 * full page load
		 *    History.events.POPSTATE: user clicked back button but browser didn't do a full page load
		 *    History.events.PAGELOAD: full browser page load, not using History API.
		 */
		context.onNavigate( (err, page, path, type) => {
			logger.debug('Executing navigate action');

			// if this is a History.events.PUSHSTATE navigation, we should change the URL in the bar location bar
			// before rendering.
			// note that for browsers that do not have pushState, this will result in a window.location change
			// and full browser load. It's kind of late to do that, as we may have waited for handleRoute to
			// finish asynchronously. perhaps we should have an "URLChanged" event that happens before "NavigateDone".
			if (type === History.events.PUSHSTATE && this._history) {
				this._history.pushState(null, null, path);
			}

			if (err) {
				// redirects are sent as errors, so let's handle it if that's the case.
				if (err.status && (err.status === 301 || err.status === 302)) {
					if (!err.redirectUrl) {
						console.error("A redirect status was sent without a corresponding redirect redirectUrl.", err);
					} else {
						setTimeout(() => {
							this._history.replaceState(null, null, err.redirectUrl);
							this.context.navigate(new ClientRequest(err.redirectUrl));
						}, 0);
					}
				} else {
					logger.error("onNavigate error", err);
				}
				return;
			}

			var routeName = context.navigator.getCurrentRoute().name;

			if (!this._previouslyRendered) {
				cssHelper.registerPageLoad(routeName);
			} else {
				// render the document title.
				this._renderTitle(page);

				// render the base tag.
				this._renderBase(page);

				this._renderMetaTags(page);

			}

			cssHelper.ensureCss(routeName, page);

			page.getBodyClasses().then((classes) => {
				classes.push(`route-${routeName}`);
				document.body.className = classes.join(' ');
			}).then(() => this._render(page)).catch((err) => {
				logger.error("Error while rendering", err.stack);
			});

		});

	}

	_renderTitle(page) {
		page.getTitle().then(newTitle => {
			if (newTitle && newTitle !== document.title) {
				document.title = newTitle;
			}
		}).catch(err => console.error("Error while setting the document title", err));
	}

	_renderBase(page) {
		page.getBase().then(base => {
			var currentBaseTag = document.head.querySelector("head base");
			if (base === null) {
				// get rid of the current base tag.
				if (currentBaseTag) currentBaseTag.parentNode.removeChild(currentBaseTag);
			} else {
				// we need a base tag. add one if it's not there yet.
				if (!currentBaseTag) {
					currentBaseTag = document.createElement("base");
					document.head.appendChild(currentBaseTag);
				}
				currentBaseTag.href = base.href;
				if (base.target) currentBaseTag.target = base.target;
			}

		});
	}

	_renderMetaTags(page) {
		// first, remove all the current meta tags.
		var currentMetaTags = document.head.querySelectorAll("meta");
		for (var i = 0; i < currentMetaTags.length; i++) {
			currentMetaTags[i].parentNode.removeChild(currentMetaTags[i]);
		}

		// now add all the meta tags for the new page.
		page.getMetaTags().forEach((metaTagPromise) => {
			metaTagPromise.then((metaTag) => {
				var parent = document.head;
				if (metaTag.noscript) {
					var noscript = document.createElement("noscript");
					parent.appendChild(noscript);
					parent = noscript;
				}

				var meta = document.createElement("meta");
				["name", "httpEquiv", "charset", "content"].forEach((name) => {
					if (metaTag[name]) meta[name] = metaTag[name];
				});

				parent.appendChild(meta);
			});
		});
	}

	_render (page) {
		var t0 = new Date;
		logger.debug('React Rendering');

		// if we were previously rendered on the client, clean up the old divs and
		// their ReactComponents.
		this._cleanupPreviousRender(this.mountNode);

		// if we are reattaching to server-generated HTML, we should find the root elements that were sent down.
		// we store them in a temp array indexed by their triton-root-id.
		var serverRenderedRoots = [];
		this._getRootElements(this.mountNode).forEach((rootElement) => {
			serverRenderedRoots[rootElement.getAttribute(TRITON_DATA_ATTRIBUTE)] = rootElement;
		});

		var elementPromises = PageUtil.standardizeElements(page.getElements());

		var newRenderedElements = [];
		var renderElement = (element, index) => {
			// if someone already rendered this element, let's not!
			if (newRenderedElements[index]) return;

			// for each ReactElement that we want to render, either use the server-rendered root element, or
			// create a new root element.
			logger.debug("Rendering root node #" + index);
			var root = serverRenderedRoots[index] || this._createTritonRootNode(this.mountNode, index);

			// TODO: get rid of context once continuation-local-storage holds our important context vars.
			element = React.addons.cloneWithProps(element, { context: this.context });
			newRenderedElements[index] = React.render(element, root);
			if (index === elementPromises.length - 1) {
				logger.debug('React Rendered');
				logger.time('render', new Date - t0);
				this.emit("render");
			}
		};

		// if and when the loader runs out of cache, we should render everything we have synchronously through EarlyPromises.
		// this lets us render when the server timed out.
		// TODO: should we ONLY do this when the server times out?
		TritonAgent.cache().whenCacheDepleted().then(() => {
			logger.debug("Loader cache depleted; rendering elements.");
			elementPromises.forEach((elementEarlyPromise, index) => {
				renderElement(elementEarlyPromise.getValue(), index);
			});
		});

		// I find the control flow for chaining promises impossibly mind-bending, but what I intended was something
		// like:
		//
		//		elementPromises.forEach((elementPromise, index) => {
		//			var element = await elementPromise;
		//			renderElement(element, index);
		//		});
		//		this._previouslyRendered = true;
		//		this.emit("render");
		//
		// if it was using ES7 async/await syntax. I learned how to chain an array of promises in ES5/6
		// from http://bahmutov.calepin.co/chaining-promises.html , and apparently it looks like the code below. But
		// if I got something wrong, what I intended was the control flow expressed in this comment.
		return elementPromises.reduce((chain, next, index) => {
			return chain.then((element) => {
		 		renderElement(element, index - 1);
				return next;
			})
		}).then((element) => {
			// reduce is called length - 1 times. we need to call one final time here to make sure we
			// chain the final promise.
	 		renderElement(element, elementPromises.length - 1);

			this._previouslyRendered = true;
		}).catch((err) => {
			logger.error("Error while rendering.", err.stack);
		});
	}

	/**
	 * Cleans up a previous React render in the document. Unmounts all the components and destoys the mounting
	 * DOM node(s) that were created.
	 */
	_cleanupPreviousRender(mountNode) {
		if (this._previouslyRendered) {
			logger.debug("Removing previous page's React components");

			this._getRootElements(mountNode).forEach((tritonRoot) => {
				// since this node has a "data-triton-root-id" attribute, we can assume that we created it and
				// should destroy it. Destruction means first unmounting from React and then destroying the DOM node.
				React.unmountComponentAtNode(tritonRoot);
				mountNode.removeChild(tritonRoot);
			});
		}
	}

	/**
	 * Returns an array of all of the root elements that are children of mountNode. The root elements
	 * should all have a "data-triton-root-id" attribute and be direct children of mountNode.
	 */
	_getRootElements(mountNode) {
		// if children returned an array instead of a NodeList, we could use .filter(), but
		// alas, it does not. We could copy it over to an array, but seems easier to just iterate
		// over the NodeList.
		var potentialRoots = mountNode.children;
		var result = [];
		for (var i = 0; i < potentialRoots.length; i++) {
			var potentialRoot = potentialRoots[i];
			if (potentialRoot.hasAttribute(TRITON_DATA_ATTRIBUTE)) {
				// since this node has a "data-triton-root-id" attribute, we can assume that we created it.
				result.push(potentialRoot);
			} else {
				// it's deeply troubling that there's a div we didn't create, but for now, just warn, and
				// don't obliterate the node.
				console.warn("Found an element inside Triton's rendering canvas that did not have data-triton-root-id " +
					"and was probably not created by Triton. Other code may be manually mucking with the DOM, which could " +
					"cause unpredictable behavior", potentialRoot);
			}
		}
		return result;
	}

	/**
	 * This method creates a new div to render a ReactElement in to at the end of the mount node.
	 */
	_createTritonRootNode(mountNode, index) {
		var root = document.createElement("div");
		root.setAttribute(TRITON_DATA_ATTRIBUTE, index);
		mountNode.appendChild(root);
		return root;
	}

	init () {

		var unloadHandler = () => {this.terminate();};

		if (window && window.addEventListener) {
		    window.addEventListener("unload", unloadHandler);
		}
		else if (window && window.attachEvent) {
		    window.attachEvent("onunload", unloadHandler);
		}

		var location = window.location;
		var path = location.pathname + location.search;
		this._initializeHistoryListener(this.context);
		this.context.navigate(new ClientRequest(path));
	}

	/**
	 * Initializes us to listen to back button events. When the user presses the back button, the history
	 * listener will be called and cause a navigate() event.
	 */
	_initializeHistoryListener(context) {

        // Disabled for 61.
        // FIXME: Either make this work well or eliminate it.
        return;

        this._historyListener = (e) => {
            if (context) {
                var path = this._history.getPath();

                // pass in "popstate" because this is when a user clicks the back button.
                context.navigate(new ClientRequest(path), History.events.POPSTATE);

            }
        };

        this._history = new History();
        this._history.on(this._historyListener);
	}

	_setupLateArrivalHandler () {
		// used by <script> callbacks to register data sent down on the
		// initial connection after initial render
		window.__lateArrival = this.lateArrival.bind(this);
	}

	lateArrival (url, resString) {
		this._initialRenderDfd.promise.done( () => {
			TritonAgent.cache().lateArrival(url, JSON.parse(decodeURIComponent(resString)));
		});
	}

}

function checkNotEmpty(state, key) {
	if (typeof state[key] === 'undefined') {
		var msg = key + ' not defined in dehydrated state';
		logger.error(msg)
		throw new Error(msg);
	}
}

function buildConfig(dehydratedConfig) {
	// rehydrate the config object
	var config = require("./config")();
	config.rehydrate(dehydratedConfig);
	return config;
}

function buildContext(dehydratedContext, routes) {
	var context = new RequestContext.Builder()
		.setRoutes(routes)
		.create();
	context.rehydrate(dehydratedContext);
	return context;
}

module.exports = ClientController;

<|endoftext|>


<commit_before>morpheus.HelpMenu = function() {
	var html = [];
	html.push('
');
	html
			.push('');
	html
			.push('
');
	html.push('
Contact
');
	//

	html.push('
Linking
');
	html.push('
Tutorial
');

	html.push('
');
	html.push('
');
	this.$el = $(html.join(''));
	this.$el.find('button').on('click', function(e) {
		e.stopImmediatePropagation();
	});
	this.$el.find('[name=contact]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open('mailto:morpheus@broadinstitute.org');
	});
	this.$el.find('[name=tutorial]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open(morpheus.Util.URL + 'tutorial.html');

	});

	this.$el.find('[name=linking]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open(morpheus.Util.URL + 'linking.html');
	});
};<commit_msg>added link to source code<commit_after>morpheus.HelpMenu = function() {
	var html = [];
	html.push('
');
	html
			.push('');
	html
			.push('
');
	html.push('
Contact
');
	//

	html.push('
Linking
');
	html.push('
Tutorial
');

	html.push('
');
	html.push('
');
	this.$el = $(html.join(''));
	this.$el.find('button').on('click', function(e) {
		e.stopImmediatePropagation();
	});
	this.$el.find('[name=contact]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open('mailto:morpheus@broadinstitute.org');
	});
	this.$el.find('[name=tutorial]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open(morpheus.Util.URL + 'tutorial.html');

	});

	this.$el.find('[name=linking]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open(morpheus.Util.URL + 'linking.html');
	});
	this.$el.find('[name=source]').on('click', function(e) {
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		window.open('https://github.com/joshua-gould/morpheus.js');
	});
};<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { hashHistory } from 'react-router';
import {
  setMessage,
  setDerFile,
  setFilter,
  setFilesList,
  setDer,
  setOption,
  setOptionStorage,
  initConfig
} from '../store/actions';

const DerContainer = require('./../components/DerContainer/DerContainer.js');
const Message = require('./../components/Message/Message.js');
const Button = require('./../components/Button/Button.js');

class App extends Component{
  constructor(props) {
    super(props);
    this.state = {
      tts: this.props.route.config.tts,
      exit: this.props.route.config.exit,
      mode: this.props.route.config.defaultMode,
      searchableElement: null,
      openedMenu: ''
    }
  }

  componentWillMount() {
    // console.log(this.props.route.config.tts);
    this.props.setOption('tts', this.props.route.config.tts);
    this.props.setOption('exit', this.props.route.config.exit);
    this.props.initConfig();
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.routes[nextProps.routes.length-1].path === 'file') {
      this.context.router.push('/menu');
    }
  }

  toggleMenu(id, labelOnClose, labelOnOpen) {
    let open = this.state.openedMenu === id;
    this.setState({
      openedMenu: open ? '' : id
    }, () => {
      if (open && labelOnClose) {
        hashHistory.push('/');
        this.read(labelOnClose);
      } else if (labelOnOpen) {
        hashHistory.push(id);
        this.read(labelOnOpen);
      }
    });
  }

  showMessage(text, type) {
    this.props.setMessage({text: text, type: type});
    this.read(text);
  }

  changeDerFile(file) {
    if (file !== null) {
      this.context.router.push('/');
      this.read('Nouveau document sélectionné, fermeture du menu').then(() => {
        this.toggleMenu('menu');
        this.props.setDerFile(file);
      });
    } else {
      this.read('Aucun fichier sélectionné, retour au menu');
      this.context.router.push('/menu');
    }
  }

  changeMode(mode) {
    this.setState({mode: mode});
  }

  setSearchableElement(searchableElement) {
    this.setState({searchableElement: searchableElement});
  }

  getMessage() {
    const {message} = this.props;
    if (message) {
      return <Message text={message.text} type={message.messageType} />
    }
  }

  read(text) {
    if (this.props.config.tts) {
      this.props.config.tts.speak(text)
    }
  }

  render() {
    const { mode, searchableElement } = this.state;
    const { config, der, selectedDocument, derFile, activeFilter } = this.props;
    let navigation;
    if (this.props.children) {
      navigation = React.cloneElement(this.props.children, {
        config: config,
        options: {
          tts: this.state.tts,
          der, selectedDocument, derFile, activeFilter
        },
        actions: {
          toggleMenu: this.toggleMenu.bind(this),
          showMessage: (text, type) => this.showMessage(text, type),
          setFilesList: files => this.props.setFilesList(files),
          setDer: der => this.props.setDer(der),
          changeDerFile: file => this.changeDerFile(file),
          changeDocument: this.changeDocument,
          changeMode: this.changeMode,
          changeFilter: filter => this.props.setFilter(filter),
          setSearchableElement: this.setSearchableElement,
          setOptionFormat: (format) => this.props.setOptionFormat(format)
        }
      });
    }
    return (

        <Button
          id="menu"
          tts={this.state.tts}
          labelClosed="Menu"
          labelOnClose="Fermeture du menu"
          labelOpened="Fermer le menu"
          labelOnOpen="Ouverture du menu"
          open={this.state.openedMenu === 'menu'}
          toggleMenu={this.toggleMenu.bind(this)}
           />
        <Button
          id="filters"
          tts={this.state.tts}
          labelClosed="Filtres"
          labelOnClose="Fermeture des filtres"
          labelOpened="Fermer les filtres"
          labelOnOpen="Ouverture des filtres"
          open={this.state.openedMenu === 'filters'}
          toggleMenu={this.toggleMenu.bind(this)}
          />

        {this.getMessage()}

        <DerContainer
          setFilesList={files => this.props.setFilesList(files)}
          setDer={der => this.props.setDer(der)}
          der={der}
          selectedDocument={selectedDocument}
          searchableElement={searchableElement}
          message={(text, type) => this.showMessage(text, type)}
          tts={this.state.tts}
          mode={mode}
          filter={activeFilter}
          derFile={derFile} />

        { navigation || '' }

    );
  }
}

App.contextTypes = {
  router: React.PropTypes.object.isRequired
};

const mapStateToProps = (state, ownProps) => {
  return {
    ...state.appReducer
  }
};

const mapDispatchToProps = dispatch => {
  return {
    setMessage: message => dispatch(setMessage(message)),
    setDerFile: file => dispatch(setDerFile(file)),
    setFilter: filter => dispatch(setFilter(filter)),
    setFilesList: files => dispatch(setFilesList(files)),
    setDer: files => dispatch(setDer(files)),
    setOption: (name, value) => dispatch(setOption(name, value)),
    setOptionFormat: value => dispatch(setOptionStorage('format', value)),
    initConfig: () => dispatch(initConfig())
  }
};

export default connect(mapStateToProps, mapDispatchToProps)(App);<commit_msg>Fix document load (read promise)<commit_after>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { hashHistory } from 'react-router';
import {
  setMessage,
  setDerFile,
  setFilter,
  setFilesList,
  setDer,
  setOption,
  setOptionStorage,
  initConfig
} from '../store/actions';

const DerContainer = require('./../components/DerContainer/DerContainer.js');
const Message = require('./../components/Message/Message.js');
const Button = require('./../components/Button/Button.js');

class App extends Component{
  constructor(props) {
    super(props);
    this.state = {
      tts: this.props.route.config.tts,
      exit: this.props.route.config.exit,
      mode: this.props.route.config.defaultMode,
      searchableElement: null,
      openedMenu: ''
    }
  }

  componentWillMount() {
    // console.log(this.props.route.config.tts);
    this.props.setOption('tts', this.props.route.config.tts);
    this.props.setOption('exit', this.props.route.config.exit);
    this.props.initConfig();
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.routes[nextProps.routes.length-1].path === 'file') {
      this.context.router.push('/menu');
    }
  }

  toggleMenu(id, labelOnClose, labelOnOpen) {
    let open = this.state.openedMenu === id;
    this.setState({
      openedMenu: open ? '' : id
    }, () => {
      if (open && labelOnClose) {
        hashHistory.push('/');
        this.read(labelOnClose);
      } else if (labelOnOpen) {
        hashHistory.push(id);
        this.read(labelOnOpen);
      }
    });
  }

  showMessage(text, type) {
    this.props.setMessage({text: text, type: type});
    this.read(text);
  }

  changeDerFile(file) {
    if (file !== null) {
      this.context.router.push('/');
      this.read('Nouveau document sélectionné, fermeture du menu').then(() => {
        this.toggleMenu('menu');
        this.props.setDerFile(file);
      });
    } else {
      this.read('Aucun fichier sélectionné, retour au menu');
      this.context.router.push('/menu');
    }
  }

  changeMode(mode) {
    this.setState({mode: mode});
  }

  setSearchableElement(searchableElement) {
    this.setState({searchableElement: searchableElement});
  }

  getMessage() {
    const {message} = this.props;
    if (message) {
      return <Message text={message.text} type={message.messageType} />
    }
  }

  read(text) {
    if (this.props.config.tts) {
      return this.props.config.tts.speak(text)
    }
  }

  render() {
    const { mode, searchableElement } = this.state;
    const { config, der, selectedDocument, derFile, activeFilter } = this.props;
    let navigation;
    if (this.props.children) {
      navigation = React.cloneElement(this.props.children, {
        config: config,
        options: {
          tts: this.state.tts,
          der, selectedDocument, derFile, activeFilter
        },
        actions: {
          toggleMenu: this.toggleMenu.bind(this),
          showMessage: (text, type) => this.showMessage(text, type),
          setFilesList: files => this.props.setFilesList(files),
          setDer: der => this.props.setDer(der),
          changeDerFile: file => this.changeDerFile(file),
          changeDocument: this.changeDocument,
          changeMode: this.changeMode,
          changeFilter: filter => this.props.setFilter(filter),
          setSearchableElement: this.setSearchableElement,
          setOptionFormat: (format) => this.props.setOptionFormat(format)
        }
      });
    }
    return (

        <Button
          id="menu"
          tts={this.state.tts}
          labelClosed="Menu"
          labelOnClose="Fermeture du menu"
          labelOpened="Fermer le menu"
          labelOnOpen="Ouverture du menu"
          open={this.state.openedMenu === 'menu'}
          toggleMenu={this.toggleMenu.bind(this)}
           />
        <Button
          id="filters"
          tts={this.state.tts}
          labelClosed="Filtres"
          labelOnClose="Fermeture des filtres"
          labelOpened="Fermer les filtres"
          labelOnOpen="Ouverture des filtres"
          open={this.state.openedMenu === 'filters'}
          toggleMenu={this.toggleMenu.bind(this)}
          />

        {this.getMessage()}

        <DerContainer
          setFilesList={files => this.props.setFilesList(files)}
          setDer={der => this.props.setDer(der)}
          der={der}
          selectedDocument={selectedDocument}
          searchableElement={searchableElement}
          message={(text, type) => this.showMessage(text, type)}
          tts={this.state.tts}
          mode={mode}
          filter={activeFilter}
          derFile={derFile} />

        { navigation || '' }

    );
  }
}

App.contextTypes = {
  router: React.PropTypes.object.isRequired
};

const mapStateToProps = (state, ownProps) => {
  return {
    ...state.appReducer
  }
};

const mapDispatchToProps = dispatch => {
  return {
    setMessage: message => dispatch(setMessage(message)),
    setDerFile: file => dispatch(setDerFile(file)),
    setFilter: filter => dispatch(setFilter(filter)),
    setFilesList: files => dispatch(setFilesList(files)),
    setDer: files => dispatch(setDer(files)),
    setOption: (name, value) => dispatch(setOption(name, value)),
    setOptionFormat: value => dispatch(setOptionStorage('format', value)),
    initConfig: () => dispatch(initConfig())
  }
};

export default connect(mapStateToProps, mapDispatchToProps)(App);<|endoftext|>