<commit_before>/*jslint browser: true, unparam: true, nomen: true */

(function (tangelo, $, d3) {
    "use strict";

    if (!($ && $.widget && d3)) {
        $.fn.dendrogram = tangelo.unavailable({
            plugin: "dendrogram",
            required: ["JQuery", "JQuery UI", "d3"]
        });
        return;
    }

    $.widget("tangelo.dendrogram", {
        options: {
            label: null,
            distance: null,
            id: null,
            margin: {
                top: 20,
                right: 120,
                bottom: 20,
                left: 120
            },
            nodeLimit: null,
            duration: 750,
            root: null,
            data: null,
            mode: "hide",
            nodesize: 7.5,
            textsize: 10,
            orientation: "horizontal",
            lineStyle: "curved",
            initialize: null
        },

        _missing: {
            label: "",
            distance: 1,
            id: 0
        },

        orientation: {
            abscissa: "y",
            ordinate: "x",
            heightvar: "height",
            widthvar: "width",
            xfunc: function (_, v) {
                return v;
            }
        },

        _lineStyle: function (line, s, t) {
            return line({
                source: s,
                target: t
            });
        },

        _create: function () {
            var options,
                that = this;

            if (this.options.orientation === "vertical") {
                this.orientation = {
                    abscissa: "x",
                    ordinate: "y",
                    heightvar: "width",
                    widthvar: "height",
                    xfunc: function (that, v) {
                        // This function flips a vertical tree about its
                        // midline.
                        var center = (that.xminmax[0] + that.xminmax[1]) / 2;
                        return 2 * center - v;
                    }
                };
            } else if (this.options.orientation !== "horizontal") {
                throw "illegal option for 'orientation': " + this.options.orientation;
            }

            this.tree = d3.layout.partition()
                .value(function () { return 1; })
                .sort(d3.ascending);

            // Create a d3 line drawer, including an abstracted method that can
            // call the specific line function the right way.
            this.line = null;
            if (this.options.lineStyle === "curved") {
                this.line = d3.svg.diagonal()
                    .projection(function (d) {
                        return [that.orientation.xfunc(that, d[that.orientation.abscissa]), d[that.orientation.ordinate]];
                    });
            } else if (this.options.lineStyle === "axisAligned") {
                this.line = d3.svg.line()
                    .interpolate("step-before")
                    .x(function (d) {
                        return that.orientation.xfunc(that, d[that.orientation.abscissa]);
                    })
                    .y(function (d) {
                        return d[that.orientation.ordinate];
                    });

                this._lineStyle = function (line, s, t) {
                    return line([s, t]);
                };
            } else {
                throw "illegal option for 'lineStyle': " + this.options.lineStyle;
            }

            this.svg = d3.select(this.element.get(0))
                .append("svg")
                .append("g");

            options = $.extend(true, {}, this.options);
            delete options.disabled;
            delete options.create;
            this._setOptions(options);
        },

        _setOption: function (key, value) {
            if (key === "label" || key === "distance" || key === "id") {
                this._super(key, tangelo.accessor(value, this._missing[key]));
            } else {
                this._super(key, value);
            }
        },

        _setOptions: function (options) {
            var that = this;

            $.each(options, function (key, value) {
                that._setOption(key, value);
            });

            this._update();
        },

        _update: function () {
            this.width = 1200 - this.options.margin.right - this.options.margin.left;
            this.height = 800 - this.options.margin.top - this.options.margin.bottom;

            if (!this.options.root) {
                this.options.root = this.options.data;
            }

            if (!this.options.mode) {
                this.options.mode = "hide";
            }

            this.tree.size([this.height, this.width]);

            d3.select(this.element.get(0))
                .select("svg")
                .attr("width", this[this.orientation.widthvar] + this.options.margin.right + this.options.margin.left)
                .attr("height", this[this.orientation.heightvar] + this.options.margin.top + this.options.margin.bottom)
                .select("g")
                .attr("transform", "translate(" + this.options.margin.left + "," + this.options.margin.top + ")");

            //this.options.root.x0 = this.height / 2;
            this.options.root.x0 = this[this.orientation.heightvar] / 2;
            this.options.root.y0 = 0;

            // Compute the new tree layout.
            var nodes = this.tree.nodes(this.options.root).reverse(),
                links = this.tree.links(nodes),
                source = this.options.source || this.options.root,
                node,
                nodeEnter,
                nodeUpdate,
                nodeExit,
                link,
                maxY,
                visibleLeaves,
                filteredNodes,
                filteredLinks,
                that = this;

            visibleLeaves = 0;
            function setPosition(node, pos) {
                var xSum = 0;
                node.y = pos;
                node.x = node.x + node.dx / 2;
                if (!node.parent) {
                    node.parent = node;
                }
                if (node.children) {
                    node.children.forEach(function (d) {
                        d.parent = node;
                        setPosition(d, pos + 10 * that.options.distance(d));
                        xSum += d.x;
                    });
                    node.x = xSum / node.children.length;
                } else {
                    visibleLeaves += 1;
                }
            }
            setPosition(this.options.root, 0);

            // Compute the leftmost and rightmost positions in the tree.
            function minmax(node) {
                var leftmost,
                    rightmost,
                    p;

                leftmost = node;
                while (leftmost.children && leftmost.children[0]) {
                    leftmost = leftmost.children[0];
                }

                rightmost = node;
                while (rightmost.children && rightmost.children[1]) {
                    rightmost = rightmost.children[1];
                }

                return [leftmost.x, rightmost.x];
            }
            this.xminmax = minmax(this.options.root);

            // Normalize Y to fill space
            maxY = d3.extent(nodes, function (d) {
                return d.y;
            })[1];
            nodes.forEach(function (d) {
                d.y = d.y / maxY * (that.width - 150);
            });

            if (this.options.nodeLimit && nodes.length > this.options.nodeLimit) {
                // Filter out everything beyond parent y-position to keep things interactive
                nodes.sort(function (a, b) {
                    return d3.ascending(a.parent.y, b.parent.y);
                });
                nodes.forEach(function (d, i) {
                    d.index = i;
                });
                filteredNodes = nodes.slice(0, this.options.nodeLimit);
                maxY = filteredNodes[filteredNodes.length - 1].parent.y;
                filteredNodes.forEach(function (d) {
                    d.y = d.y > maxY ? maxY : d.y;
                });

                // Filter the links based on visible nodes
                filteredLinks = [];
                links.forEach(function (d) {
                    if (d.source.index < this.options.nodeLimit && d.target.index < this.options.nodeLimit) {
                        filteredLinks.push(d);
                    }
                });
                nodes = filteredNodes;
                links = filteredLinks;
            }

            // Toggle children on click.
            function click(d) {
                if (that.options.mode === "hide") {
                    if (d.children) {
                        d._children = d.children;
                        d.children = null;
                        d3.select(this)
                            .select("circle")
                            .classed("children-hidden", true);
                    } else {
                        d.children = d._children;
                        d._children = null;
                        d3.select(this)
                            .select("circle")
                            .classed("children-hidden", false);
                    }
                } else if (that.options.mode === "focus") {
                    that.options.root = d;
                } else if (that.options.mode === "label") {
                    d.showLabel = d.showLabel ? false : true;
                }
                //that._update({source: d});
                that._setOptions({source: d});
            }

            function firstChild(d) {
                if (d.children) {
                    return firstChild(d.children[0]);
                }
                if (d._children) {
                    return firstChild(d._children[0]);
                }
                return d;
            }

            function lastChild(d) {
                if (d.children) {
                    return lastChild(d.children[d.children.length - 1]);
                }
                if (d._children) {
                    return lastChild(d._children[d._children.length - 1]);
                }
                return d;
            }

            function leafCount(d) {
                var children = d.children,
                    sum = 0;
                if (!children) {
                    children = d._children;
                }

                // I am an internal node, so total the leaves of the children
                if (children) {
                    children.forEach(function (child) {
                        sum += leafCount(child);
                    });
                    return sum;
                }

                // I am a leaf
                return 1;
            }

            // Update the nodes…
            node = this.svg.selectAll("g.node")
                .data(nodes, function (d) {
                    return that.options.id(d);
                });

            // Enter any new nodes at the parent's previous position.
            nodeEnter = node.enter()
                .append("g")
                .classed("node", true)
                .attr("transform", function () {
                    return "translate(" + that.orientation.xfunc(that, source[that.orientation.abscissa + "0"]) + "," + source[that.orientation.ordinate + "0"] + ")";
                })
                .on("dblclick", click);

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .classed("node", true)
                .on("mouseenter", function () {
                    d3.select(this)
                        .classed("hovering", true);
                })
                .on("mouseleave", function () {
                    d3.select(this)
                        .classed("hovering", false);
                });

            nodeEnter.append("text")
                .attr("x", 10)
                .attr("dy", ".35em")
                .attr("text-anchor", "start")
                .style("font-size", this.options.textsize + "px")
                .text(this.options.label)
                .style("fill-opacity", 1e-6);

            // Transition nodes to their new position.
            nodeUpdate = node.transition()
                .duration(this.options.duration)
                .attr("transform", function (d) {
                    return "translate(" + that.orientation.xfunc(that, d[that.orientation.abscissa]) + "," + d[that.orientation.ordinate] + ")";
                });

            nodeUpdate.select("circle")
                .attr("r", this.options.nodesize);

            nodeUpdate.select("text")
                .text(function (d) {
                    var label = that.options.label(d);
                    if (d._children || (d.children && d.showLabel)) {
                        if (label === "") {
                            label = that.options.label(firstChild(d)) + " ... " + that.options.label(lastChild(d));
                        }
                        return label + " (" + leafCount(d) + ")";
                    }
                    if (visibleLeaves < that.height / (0.8 * that.options.textsize)) {
                        return label;
                    }
                    return "";
                })
                .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            nodeExit = node
                .exit()
                .transition()
                .duration(this.options.duration)
                .attr("transform", function () {
                    return "translate(" + that.orientation.xfunc(that, source[that.orientation.abscissa]) + "," + source[that.orientation.ordinate] + ")";
                })
                .remove();

            nodeExit.select("circle")
                .attr("r", 1e-6);

            nodeExit.select("text")
                .style("fill-opacity", 1e-6);

            // Update the links…
            link = this.svg.selectAll("path.link")
                .data(links, function (d) {
                    return that.options.id(d.target);
                });

            // Enter any new links at the parent's previous position.
            link.enter()
                .insert("path", "g")
                .classed("link", true)
                .style("stroke", "black")
                .style("stroke-width", "1px")
                .style("fill", "none")
                .attr("d", function () {
                    var o = {x: source.x0, y: source.y0};
                    return that._lineStyle(that.line, o, o);
                });

            // Transition links to their new position.
            link.transition()
                .duration(this.options.duration)
                .attr("d", function (d) {
                    return that._lineStyle(that.line, d.source, d.target);
                });

            // Transition exiting nodes to the parent's new position.
            link.exit()
                .transition()
                .duration(this.options.duration)
                .attr("d", function () {
                    var o = {x: source.x, y: source.y};
                    return that._lineStyle(that.line, o, o);
                })
                .remove();

            // Stash the old positions for transition.
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            if (this.options.initialize) {
                this.options.initialize(nodeEnter, node, nodeExit);
            }
        },

        download: function (format) {
            var node,
                s,
                d,
                str;

            if (format === "pdf") {
                node = this.svg
                    .selectAll("g.node")
                    .select("circle")
                    .attr("r", function (d) {
                        return d._children ? this.options.nodesize : 0;
                    });
                s = new window.XMLSerializer();
                d = d3.select("svg").node();
                str = s.serializeToString(d);

                // Change back to normal
                node.attr("r", this.options.nodesize);

                d3.json("/service/svg2pdf")
                    .send("POST", str, function (error, data) {
                        window.location = "/service/svg2pdf?id=" + data.result;
                    });
            } else {
                window.alert("Unsupported export format type: " + format);
            }
        },

        reset: function () {
            function unhideAll(d) {
                if (!d.children) {
                    d.children = d._children;
                    d._children = null;
                }
                if (d.children) {
                    d.children.forEach(unhideAll);
                }
            }
            unhideAll(this.options.data);
            this._setOptions({
                root: this.options.data
            });
        }
    });
}(window.tangelo, window.jQuery, window.d3));
<commit_msg>Simplifying abstracted line function (no longer needs to be passed "this" argument).<commit_after>/*jslint browser: true, unparam: true, nomen: true */

(function (tangelo, $, d3) {
    "use strict";

    if (!($ && $.widget && d3)) {
        $.fn.dendrogram = tangelo.unavailable({
            plugin: "dendrogram",
            required: ["JQuery", "JQuery UI", "d3"]
        });
        return;
    }

    $.widget("tangelo.dendrogram", {
        options: {
            label: null,
            distance: null,
            id: null,
            margin: {
                top: 20,
                right: 120,
                bottom: 20,
                left: 120
            },
            nodeLimit: null,
            duration: 750,
            root: null,
            data: null,
            mode: "hide",
            nodesize: 7.5,
            textsize: 10,
            orientation: "horizontal",
            lineStyle: "curved",
            initialize: null
        },

        _missing: {
            label: "",
            distance: 1,
            id: 0
        },

        orientation: {
            abscissa: "y",
            ordinate: "x",
            heightvar: "height",
            widthvar: "width",
            xfunc: function (_, v) {
                return v;
            }
        },

        _lineStyle: function (s, t) {
            return this.line({
                source: s,
                target: t
            });
        },

        _create: function () {
            var options,
                that = this;

            if (this.options.orientation === "vertical") {
                this.orientation = {
                    abscissa: "x",
                    ordinate: "y",
                    heightvar: "width",
                    widthvar: "height",
                    xfunc: function (that, v) {
                        // This function flips a vertical tree about its
                        // midline.
                        var center = (that.xminmax[0] + that.xminmax[1]) / 2;
                        return 2 * center - v;
                    }
                };
            } else if (this.options.orientation !== "horizontal") {
                throw "illegal option for 'orientation': " + this.options.orientation;
            }

            this.tree = d3.layout.partition()
                .value(function () { return 1; })
                .sort(d3.ascending);

            // Create a d3 line drawer, including an abstracted method that can
            // call the specific line function the right way.
            this.line = null;
            if (this.options.lineStyle === "curved") {
                this.line = d3.svg.diagonal()
                    .projection(function (d) {
                        return [that.orientation.xfunc(that, d[that.orientation.abscissa]), d[that.orientation.ordinate]];
                    });
            } else if (this.options.lineStyle === "axisAligned") {
                this.line = d3.svg.line()
                    .interpolate("step-before")
                    .x(function (d) {
                        return that.orientation.xfunc(that, d[that.orientation.abscissa]);
                    })
                    .y(function (d) {
                        return d[that.orientation.ordinate];
                    });

                this._lineStyle = function (s, t) {
                    return that.line([s, t]);
                };
            } else {
                throw "illegal option for 'lineStyle': " + this.options.lineStyle;
            }

            this.svg = d3.select(this.element.get(0))
                .append("svg")
                .append("g");

            options = $.extend(true, {}, this.options);
            delete options.disabled;
            delete options.create;
            this._setOptions(options);
        },

        _setOption: function (key, value) {
            if (key === "label" || key === "distance" || key === "id") {
                this._super(key, tangelo.accessor(value, this._missing[key]));
            } else {
                this._super(key, value);
            }
        },

        _setOptions: function (options) {
            var that = this;

            $.each(options, function (key, value) {
                that._setOption(key, value);
            });

            this._update();
        },

        _update: function () {
            this.width = 1200 - this.options.margin.right - this.options.margin.left;
            this.height = 800 - this.options.margin.top - this.options.margin.bottom;

            if (!this.options.root) {
                this.options.root = this.options.data;
            }

            if (!this.options.mode) {
                this.options.mode = "hide";
            }

            this.tree.size([this.height, this.width]);

            d3.select(this.element.get(0))
                .select("svg")
                .attr("width", this[this.orientation.widthvar] + this.options.margin.right + this.options.margin.left)
                .attr("height", this[this.orientation.heightvar] + this.options.margin.top + this.options.margin.bottom)
                .select("g")
                .attr("transform", "translate(" + this.options.margin.left + "," + this.options.margin.top + ")");

            //this.options.root.x0 = this.height / 2;
            this.options.root.x0 = this[this.orientation.heightvar] / 2;
            this.options.root.y0 = 0;

            // Compute the new tree layout.
            var nodes = this.tree.nodes(this.options.root).reverse(),
                links = this.tree.links(nodes),
                source = this.options.source || this.options.root,
                node,
                nodeEnter,
                nodeUpdate,
                nodeExit,
                link,
                maxY,
                visibleLeaves,
                filteredNodes,
                filteredLinks,
                that = this;

            visibleLeaves = 0;
            function setPosition(node, pos) {
                var xSum = 0;
                node.y = pos;
                node.x = node.x + node.dx / 2;
                if (!node.parent) {
                    node.parent = node;
                }
                if (node.children) {
                    node.children.forEach(function (d) {
                        d.parent = node;
                        setPosition(d, pos + 10 * that.options.distance(d));
                        xSum += d.x;
                    });
                    node.x = xSum / node.children.length;
                } else {
                    visibleLeaves += 1;
                }
            }
            setPosition(this.options.root, 0);

            // Compute the leftmost and rightmost positions in the tree.
            function minmax(node) {
                var leftmost,
                    rightmost,
                    p;

                leftmost = node;
                while (leftmost.children && leftmost.children[0]) {
                    leftmost = leftmost.children[0];
                }

                rightmost = node;
                while (rightmost.children && rightmost.children[1]) {
                    rightmost = rightmost.children[1];
                }

                return [leftmost.x, rightmost.x];
            }
            this.xminmax = minmax(this.options.root);

            // Normalize Y to fill space
            maxY = d3.extent(nodes, function (d) {
                return d.y;
            })[1];
            nodes.forEach(function (d) {
                d.y = d.y / maxY * (that.width - 150);
            });

            if (this.options.nodeLimit && nodes.length > this.options.nodeLimit) {
                // Filter out everything beyond parent y-position to keep things interactive
                nodes.sort(function (a, b) {
                    return d3.ascending(a.parent.y, b.parent.y);
                });
                nodes.forEach(function (d, i) {
                    d.index = i;
                });
                filteredNodes = nodes.slice(0, this.options.nodeLimit);
                maxY = filteredNodes[filteredNodes.length - 1].parent.y;
                filteredNodes.forEach(function (d) {
                    d.y = d.y > maxY ? maxY : d.y;
                });

                // Filter the links based on visible nodes
                filteredLinks = [];
                links.forEach(function (d) {
                    if (d.source.index < this.options.nodeLimit && d.target.index < this.options.nodeLimit) {
                        filteredLinks.push(d);
                    }
                });
                nodes = filteredNodes;
                links = filteredLinks;
            }

            // Toggle children on click.
            function click(d) {
                if (that.options.mode === "hide") {
                    if (d.children) {
                        d._children = d.children;
                        d.children = null;
                        d3.select(this)
                            .select("circle")
                            .classed("children-hidden", true);
                    } else {
                        d.children = d._children;
                        d._children = null;
                        d3.select(this)
                            .select("circle")
                            .classed("children-hidden", false);
                    }
                } else if (that.options.mode === "focus") {
                    that.options.root = d;
                } else if (that.options.mode === "label") {
                    d.showLabel = d.showLabel ? false : true;
                }
                //that._update({source: d});
                that._setOptions({source: d});
            }

            function firstChild(d) {
                if (d.children) {
                    return firstChild(d.children[0]);
                }
                if (d._children) {
                    return firstChild(d._children[0]);
                }
                return d;
            }

            function lastChild(d) {
                if (d.children) {
                    return lastChild(d.children[d.children.length - 1]);
                }
                if (d._children) {
                    return lastChild(d._children[d._children.length - 1]);
                }
                return d;
            }

            function leafCount(d) {
                var children = d.children,
                    sum = 0;
                if (!children) {
                    children = d._children;
                }

                // I am an internal node, so total the leaves of the children
                if (children) {
                    children.forEach(function (child) {
                        sum += leafCount(child);
                    });
                    return sum;
                }

                // I am a leaf
                return 1;
            }

            // Update the nodes…
            node = this.svg.selectAll("g.node")
                .data(nodes, function (d) {
                    return that.options.id(d);
                });

            // Enter any new nodes at the parent's previous position.
            nodeEnter = node.enter()
                .append("g")
                .classed("node", true)
                .attr("transform", function () {
                    return "translate(" + that.orientation.xfunc(that, source[that.orientation.abscissa + "0"]) + "," + source[that.orientation.ordinate + "0"] + ")";
                })
                .on("dblclick", click);

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .classed("node", true)
                .on("mouseenter", function () {
                    d3.select(this)
                        .classed("hovering", true);
                })
                .on("mouseleave", function () {
                    d3.select(this)
                        .classed("hovering", false);
                });

            nodeEnter.append("text")
                .attr("x", 10)
                .attr("dy", ".35em")
                .attr("text-anchor", "start")
                .style("font-size", this.options.textsize + "px")
                .text(this.options.label)
                .style("fill-opacity", 1e-6);

            // Transition nodes to their new position.
            nodeUpdate = node.transition()
                .duration(this.options.duration)
                .attr("transform", function (d) {
                    return "translate(" + that.orientation.xfunc(that, d[that.orientation.abscissa]) + "," + d[that.orientation.ordinate] + ")";
                });

            nodeUpdate.select("circle")
                .attr("r", this.options.nodesize);

            nodeUpdate.select("text")
                .text(function (d) {
                    var label = that.options.label(d);
                    if (d._children || (d.children && d.showLabel)) {
                        if (label === "") {
                            label = that.options.label(firstChild(d)) + " ... " + that.options.label(lastChild(d));
                        }
                        return label + " (" + leafCount(d) + ")";
                    }
                    if (visibleLeaves < that.height / (0.8 * that.options.textsize)) {
                        return label;
                    }
                    return "";
                })
                .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            nodeExit = node
                .exit()
                .transition()
                .duration(this.options.duration)
                .attr("transform", function () {
                    return "translate(" + that.orientation.xfunc(that, source[that.orientation.abscissa]) + "," + source[that.orientation.ordinate] + ")";
                })
                .remove();

            nodeExit.select("circle")
                .attr("r", 1e-6);

            nodeExit.select("text")
                .style("fill-opacity", 1e-6);

            // Update the links…
            link = this.svg.selectAll("path.link")
                .data(links, function (d) {
                    return that.options.id(d.target);
                });

            // Enter any new links at the parent's previous position.
            link.enter()
                .insert("path", "g")
                .classed("link", true)
                .style("stroke", "black")
                .style("stroke-width", "1px")
                .style("fill", "none")
                .attr("d", function () {
                    var o = {x: source.x0, y: source.y0};
                    return that._lineStyle(o, o);
                });

            // Transition links to their new position.
            link.transition()
                .duration(this.options.duration)
                .attr("d", function (d) {
                    return that._lineStyle(d.source, d.target);
                });

            // Transition exiting nodes to the parent's new position.
            link.exit()
                .transition()
                .duration(this.options.duration)
                .attr("d", function () {
                    var o = {x: source.x, y: source.y};
                    return that._lineStyle(o, o);
                })
                .remove();

            // Stash the old positions for transition.
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            if (this.options.initialize) {
                this.options.initialize(nodeEnter, node, nodeExit);
            }
        },

        download: function (format) {
            var node,
                s,
                d,
                str;

            if (format === "pdf") {
                node = this.svg
                    .selectAll("g.node")
                    .select("circle")
                    .attr("r", function (d) {
                        return d._children ? this.options.nodesize : 0;
                    });
                s = new window.XMLSerializer();
                d = d3.select("svg").node();
                str = s.serializeToString(d);

                // Change back to normal
                node.attr("r", this.options.nodesize);

                d3.json("/service/svg2pdf")
                    .send("POST", str, function (error, data) {
                        window.location = "/service/svg2pdf?id=" + data.result;
                    });
            } else {
                window.alert("Unsupported export format type: " + format);
            }
        },

        reset: function () {
            function unhideAll(d) {
                if (!d.children) {
                    d.children = d._children;
                    d._children = null;
                }
                if (d.children) {
                    d.children.forEach(unhideAll);
                }
            }
            unhideAll(this.options.data);
            this._setOptions({
                root: this.options.data
            });
        }
    });
}(window.tangelo, window.jQuery, window.d3));
<|endoftext|>


<commit_before>var $ = require('wetfish-basic');
var line = require('./line');
var extend = require('extend');

var loaded = false;
var transforming = false;

function radians(degrees)
{
    return  degrees * Math.PI / 180;
}

function degrees(radians)
{
    return radians * 180 / Math.PI;
}

function distance(start, end)
{
    var delta =
    {
        x: end.x - start.x,
        y: end.y - start.y
    }

    delta.x *= delta.x;
    delta.y *= delta.y;

    return Math.sqrt(delta.x + delta.y);
}

var transform =
{
    load: function(element)
    {
        $(window).on('mousedown', '.transform .handle', transform.mousedown);
        $(window).on('mousemove', transform.mousemove);
        $(window).on('mouseup', transform.mouseup);
        $(window).on('resize', transform.resize);
        loaded = true;
    },

    unload: function(element)
    {
        delete transform.element;
        delete transform.size;
        delete transform.center;
        delete transform.dimension;
        delete transform.template;
        delete transform.angle;
        delete transform.scale;

        $(window).off('mousedown', '.transform .handle', transform.mousedown);
        $(window).off('mousemove', transform.mousemove);
        $(window).off('mouseup', transform.mouseup);
        $(window).off('resize', transform.resize);
        loaded = false;
    },

    start: function(element)
    {
        // Have all of the transform events been loaded yet?
        if(!loaded)
        {
            transform.load();
        }

        // If a transformation was already started, stop it!
        if(transform.element)
        {
            transform.stop();
        }

        transform.element = element;
        line.init(element);

        var position =
        {
            x: parseInt(element.transform.translate[0]), // Because wetfish basic is awesome <3
            y: parseInt(element.transform.translate[1])
        };

        var size = $(element).size();
        transform.size = size;

        var template = $('.transform.hidden').clone();

        transform.center =
        {
            x: position.x + (size.width / 2),
            y: position.y + (size.height / 2)
        };

        // Which dimension is bigger?
        transform.dimension = (size.height > size.width) ? 'height' : 'width';

        $(template).removeClass('hidden');
        $(template).style({'height': size.height + 'px', 'width': size.width + 'px'});
        $(template).transform('translate', position.x + 'px', position.y + 'px');

        transform.angle = parseFloat($(element).data('rotate'));
        transform.scale = parseFloat($(element).data('scale'));

        // If this element has already been rotated
        if(transform.angle)
        {
            // Set the new template to the same angle
            $(template).transform('rotate', transform.angle + 'deg');
        }

        // If this element has already been scaled
        if(transform.scale)
        {
            $(template).transform('scale', transform.scale);
        }

        $('.workspace').el[0].appendChild(template);
        transform.template = template;
    },

    stop: function(element)
    {
        line.refresh();
        $(transform.template).remove();
        transform.template = false;
        transform.element = false;
    },

    mousedown: function(event)
    {
        $(this).addClass('active');

        transform.offset = parseInt($(this).data('offset'));
        transforming = true;
    },

    mousemove: function(event)
    {
        if(transforming)
        {
            line.refresh();
            line.draw(event.clientX, event.clientY);

            // Get the angle of the line relative to the origin
            var difference =
            {
                x: event.clientX - transform.center.x,
                y: event.clientY - transform.center.y
            };

            // Calculate the new size of the element based on the distance from the center to the current mouse position
            var size = distance(transform.center, {x: event.clientX, y: event.clientY}) * 2;
            var original = transform.size[transform.dimension];
            var scale = size / original;
            var angle = degrees(Math.atan2(difference.y, difference.x)) + transform.offset;
            $(transform.template).transform('rotate', angle + 'deg').transform('scale', scale);
            $(transform.element).transform('rotate', angle + 'deg').transform('scale', scale);
            $(transform.element).data('rotate', angle);
            $(transform.element).data('scale', scale);
        }
    },

    mouseup: function(event)
    {
        if(transforming)
        {
            line.refresh();
            $('.transform .handle.active').removeClass('active');
            transforming = false;
        }
    },

    resize: function(event)
    {
        if(transforming)
        {
            line.init(transform.element);
        }
    },
};

module.exports = transform;
<commit_msg>Immediately save transformations after releasing the handle<commit_after>var $ = require('wetfish-basic');
var line = require('./line');
var extend = require('extend');
var storage = require('../app/storage');

var loaded = false;
var transforming = false;

function radians(degrees)
{
    return  degrees * Math.PI / 180;
}

function degrees(radians)
{
    return radians * 180 / Math.PI;
}

function distance(start, end)
{
    var delta =
    {
        x: end.x - start.x,
        y: end.y - start.y
    }

    delta.x *= delta.x;
    delta.y *= delta.y;

    return Math.sqrt(delta.x + delta.y);
}

var transform =
{
    load: function(element)
    {
        $(window).on('mousedown', '.transform .handle', transform.mousedown);
        $(window).on('mousemove', transform.mousemove);
        $(window).on('mouseup', transform.mouseup);
        $(window).on('resize', transform.resize);
        loaded = true;
    },

    unload: function(element)
    {
        delete transform.element;
        delete transform.size;
        delete transform.center;
        delete transform.dimension;
        delete transform.template;
        delete transform.angle;
        delete transform.scale;

        $(window).off('mousedown', '.transform .handle', transform.mousedown);
        $(window).off('mousemove', transform.mousemove);
        $(window).off('mouseup', transform.mouseup);
        $(window).off('resize', transform.resize);
        loaded = false;
    },

    start: function(element)
    {
        // Have all of the transform events been loaded yet?
        if(!loaded)
        {
            transform.load();
        }

        // If a transformation was already started, stop it!
        if(transform.element)
        {
            transform.stop();
        }

        transform.element = element;
        line.init(element);

        var position =
        {
            x: parseInt(element.transform.translate[0]), // Because wetfish basic is awesome <3
            y: parseInt(element.transform.translate[1])
        };

        var size = $(element).size();
        transform.size = size;

        var template = $('.transform.hidden').clone();

        transform.center =
        {
            x: position.x + (size.width / 2),
            y: position.y + (size.height / 2)
        };

        // Which dimension is bigger?
        transform.dimension = (size.height > size.width) ? 'height' : 'width';

        $(template).removeClass('hidden');
        $(template).style({'height': size.height + 'px', 'width': size.width + 'px'});
        $(template).transform('translate', position.x + 'px', position.y + 'px');

        transform.angle = parseFloat($(element).data('rotate'));
        transform.scale = parseFloat($(element).data('scale'));

        // If this element has already been rotated
        if(transform.angle)
        {
            // Set the new template to the same angle
            $(template).transform('rotate', transform.angle + 'deg');
        }

        // If this element has already been scaled
        if(transform.scale)
        {
            $(template).transform('scale', transform.scale);
        }

        $('.workspace').el[0].appendChild(template);
        transform.template = template;
    },

    stop: function(element)
    {
        line.refresh();
        $(transform.template).remove();
        transform.template = false;
        transform.element = false;
    },

    mousedown: function(event)
    {
        $(this).addClass('active');

        transform.offset = parseInt($(this).data('offset'));
        transforming = true;
    },

    mousemove: function(event)
    {
        if(transforming)
        {
            line.refresh();
            line.draw(event.clientX, event.clientY);

            // Get the angle of the line relative to the origin
            var difference =
            {
                x: event.clientX - transform.center.x,
                y: event.clientY - transform.center.y
            };

            // Calculate the new size of the element based on the distance from the center to the current mouse position
            var size = distance(transform.center, {x: event.clientX, y: event.clientY}) * 2;
            var original = transform.size[transform.dimension];
            var scale = size / original;
            var angle = degrees(Math.atan2(difference.y, difference.x)) + transform.offset;
            $(transform.template).transform('rotate', angle + 'deg').transform('scale', scale);
            $(transform.element).transform('rotate', angle + 'deg').transform('scale', scale);
            $(transform.element).data('rotate', angle);
            $(transform.element).data('scale', scale);
        }
    },

    mouseup: function(event)
    {
        if(transforming)
        {
            line.refresh();
            $('.transform .handle.active').removeClass('active');
            transforming = false;

            // Persist changes to localstorage
            storage.update(transform.element);
        }
    },

    resize: function(event)
    {
        if(transforming)
        {
            line.init(transform.element);
        }
    },
};

module.exports = transform;
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

define(function (require, exports, module) {
    "use strict";

    var _ = require("lodash");

    var util = require("adapter/util"),
        descriptor = require("adapter/ps/descriptor"),
        adapterPS = require("adapter/ps"),
        OS = require("adapter/os"),
        UI = require("adapter/ps/ui"),
        toolLib = require("adapter/lib/tool"),
        vectorMaskLib = require("adapter/lib/vectorMask");

    var Tool = require("js/models/tool"),
        VectorTool = require("./superselect/vector"),
        TypeTool = require("./superselect/type"),
        system = require("js/util/system"),
        layerActions = require("js/actions/layers"),
        policy = require("js/actions/policy"),
        shortcuts = require("js/util/shortcuts"),
        SuperselectOverlay = require("jsx!js/jsx/tools/SuperselectOverlay"),
        EventPolicy = require("js/models/eventpolicy"),
        PolicyStore = require("js/stores/policy"),
        KeyboardEventPolicy = EventPolicy.KeyboardEventPolicy,
        PointerEventPolicy = EventPolicy.PointerEventPolicy;

    /**
     * Flag to keep track of space key, so we can start panning on empty pixels
     */
    var _spaceKeyDown;

    /**
     * @private
     */
    var _selectHandler = function () {
        var toolOptions = {
                "$AtSl": false, // Don't auto select on drag
                "$ASGr": false, // Don't auto select Groups,
                "$Abbx": true // Show transform controls
            };

        var toolStore = this.flux.store("tool"),
            applicationStore = this.flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument(),
            vectorMode = toolStore.getVectorMode();

        if (!vectorMode || !currentDocument) {
            return descriptor.playObject(toolLib.setToolOptions("moveTool", toolOptions))
                .bind(this)
                .then(function () {
                    return this.transfer(policy.setMode, PolicyStore.eventKind.POINTER,
                        UI.pointerPropagationMode.ALPHA_PROPAGATE_WITH_NOTIFY);
                });
        } else {
            var currentLayers = currentDocument.layers.selected,
                currentLayer = currentLayers.first();

            if (currentLayer.vectorMaskEnabled) {
                this.transfer(layerActions.resetLayers, currentDocument, currentLayer)
                    .bind(this)
                    .then(function () {
                        currentLayer = applicationStore.getCurrentDocument().layers.selected.first();
                        if (!currentLayer.vectorMaskEmpty) {
                            return descriptor.playObject(vectorMaskLib.activateVectorMaskEditing())
                                .bind(this)
                                .then(function () {
                                    // We are not transfering here, because we activly want to end the use of our locks
                                    this.flux.actions.tools.enterPathModalState();
                                });
                        }
                    });
            }
        }
    };

    /**
     * @private
     */
    var _deselectHandler = function () {
        return this.transfer(policy.setMode, PolicyStore.eventKind.POINTER,
            UI.pointerPropagationMode.ALPHA_PROPAGATE);
    };

    /**
     * @private
     */
    var _nativeToolName = function () {
        var toolStore = this.flux.store("tool"),
            vectorMode = toolStore.getVectorMode();

        if (vectorMode) {
            return "directSelectTool";
        } else {
            return "moveTool";
        }
    };

    /**
     * Vector Mode requires that Design Space does not swallow pointer commands
     * @private
     * @return {Array.<PointerEventPolicy>}
     */
    var _pointerPolicyList = function () {
        var toolStore = this.flux.store("tool"),
            vectorMode = toolStore.getVectorMode(),
            pointerPolicy;

        if (vectorMode) {
            pointerPolicy = new PointerEventPolicy(UI.policyAction.ALPHA_PROPAGATE,
                OS.eventKind.LEFT_MOUSE_DOWN);
        } else {
            pointerPolicy = new PointerEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.LEFT_MOUSE_DOWN);
        }
        return [pointerPolicy];
    };

    /**
     * @implements {Tool}
     * @constructor
     */
    var SuperSelectTool = function () {
        this.id = "newSelect";
        this.icon = "newSelect";
        this.name = "Super Select";
        this.nativeToolName = _nativeToolName;
        this.dragging = false;
        this.dragEvent = null;
        this.activationKey = shortcuts.GLOBAL.TOOLS.SELECT;
        this.handleVectorMaskMode = true;

        this.selectHandler = _selectHandler;
        this.deselectHandler = _deselectHandler;

        var escapeKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.ESCAPE),
            tabKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.TAB),
            deleteKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.DELETE),
            backspaceKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.BACKSPACE),
            enterKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.ENTER);

        this.keyboardPolicyList = [
            escapeKeyPolicy,
            tabKeyPolicy,
            deleteKeyPolicy,
            backspaceKeyPolicy,
            enterKeyPolicy
        ];

        this.pointerPolicyList = _pointerPolicyList;

        _spaceKeyDown = false;

        this.subToolList = [
            new VectorTool(),
            new TypeTool()
        ];
    };
    util.inherits(SuperSelectTool, Tool);

    /**
     * Handler for mouse down events, installed when the tool is active.
     *
     * @param {SyntheticEvent} event
     */
    SuperSelectTool.prototype.onMouseDown = function (event) {
        // We don't want to handle right or middle clicks!
        if (event.button !== 0) {
            return;
        }

        this.dragging = true;
        this.dragEvent = _.clone(event);
    };

    /**
     * Handler for mouse up, turns off dragging
     *
     * @param {SyntheticEvent} event
     */
    SuperSelectTool.prototype.onMouseUp = function (event) {
        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument(),
            mouseDownFlag = !!this.dragEvent,
            diveIn = false;

        if (mouseDownFlag) {
            diveIn = system.isMac ? this.dragEvent.metaKey : this.dragEvent.ctrlKey;
        }

        // Clean up even if we're canceling out
        this.dragging = false;
        this.dragEvent = null;

        // if dragEvent is null, mouse down was not hit, so we shouldn't try to click
        if (!currentDocument || !mouseDownFlag) {
            return;
        }
        flux.actions.superselect.click(currentDocument, event.pageX, event.pageY, diveIn, event.shiftKey);
    };

    /**
     * Handler for mouse move, sends a click to Photoshop at mouse down location
     */
    SuperSelectTool.prototype.onMouseMove = function () {
        if (!this.dragging) {
            return;
        }
        this.dragging = false;

        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument();

        if (!currentDocument) {
            return;
        }

        var dragEvent = this.dragEvent,
            modifiers = {
                option: dragEvent.altKey,
                command: dragEvent.metaKey,
                shift: dragEvent.shiftKey,
                control: dragEvent.ctrlKey
            };

        // Since we don't get the mouse up after we start dragging in PS, we need to reset the event here
        this.dragEvent = null;
        flux.actions.superselect.drag(currentDocument, dragEvent.pageX, dragEvent.pageY, modifiers, _spaceKeyDown);

        // If we start panning, PS is going to eat up the key up event during, so set it to false here
        _spaceKeyDown = false;
    };

    /** @ignore */
    SuperSelectTool.prototype.onClick = function (event) {
        // Prevents clicks from reaching the window and dismissing onWindowClick dialogs
        event.stopPropagation();
    };

    /**
     * Handler for mouse click events, installed when the tool is active.
     *
     * @param {SyntheticEvent} event
     */
    SuperSelectTool.prototype.onDoubleClick = function (event) {
        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument();

        if (!currentDocument) {
            return;
        }

        flux.actions.superselect.doubleClick(currentDocument, event.pageX, event.pageY);
    };

    /**
     * Handler for keydown events, installed when the tool is active.
     *
     * TODO Fix this after keyboard policies are more in place
     * @param {CustomEvent} event
     */
    SuperSelectTool.prototype.onKeyDown = function (event) {
        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            toolStore = flux.store("tool"),
            vectorMaskMode = toolStore.getVectorMode(),
            currentDocument = applicationStore.getCurrentDocument();

        if (!currentDocument) {
            return;
        }

        var detail = event.detail;

        switch (detail.keyCode) {
        case OS.eventKeyCode.ESCAPE: // Escape
            if (vectorMaskMode) {
                flux.actions.tools.changeVectorMaskMode(false);
            } else {
                var dontDeselectAll = system.isMac ? detail.modifiers.alt : detail.modifiers.shift;
                flux.actions.superselect.backOut(currentDocument, dontDeselectAll);
            }
            break;
        case OS.eventKeyCode.TAB: // Tab
            var cycleBack = detail.modifiers.shift;
            flux.actions.superselect.nextSibling(currentDocument, cycleBack);
            break;
        case OS.eventKeyCode.ENTER: // Enter
            if (vectorMaskMode) {
                adapterPS.endModalToolState(true);
            } else {
                flux.actions.superselect.diveIn(currentDocument);
            }
            break;
        case OS.eventKeyCode.DELETE: // DELETE
        case OS.eventKeyCode.BACKSPACE: // BACKSPACE
            if (toolStore.getVectorMode()) {
                flux.actions.layers.deleteVectorMask()
                    .then(function () {
                        flux.actions.tools.changeVectorMaskMode(false);
                    });
            }
        }

        if (detail.keyChar === " ") {
            _spaceKeyDown = true;
        }
    };

    /**
     * Handler for key up events, we look for space bar key up event to
     * reset the flag we send to superselect.drag
     *
     * @param {CustomEvent} event
     */
    SuperSelectTool.prototype.onKeyUp = function (event) {
        var detail = event.detail;

        if (detail.keyChar === " ") {
            _spaceKeyDown = false;
        }
    };

    SuperSelectTool.prototype.toolOverlay = SuperselectOverlay;

    module.exports = SuperSelectTool;
});
<commit_msg>respond to PR<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

define(function (require, exports, module) {
    "use strict";

    var _ = require("lodash");

    var util = require("adapter/util"),
        descriptor = require("adapter/ps/descriptor"),
        adapterPS = require("adapter/ps"),
        OS = require("adapter/os"),
        UI = require("adapter/ps/ui"),
        toolLib = require("adapter/lib/tool"),
        vectorMaskLib = require("adapter/lib/vectorMask");

    var Tool = require("js/models/tool"),
        VectorTool = require("./superselect/vector"),
        TypeTool = require("./superselect/type"),
        system = require("js/util/system"),
        layerActions = require("js/actions/layers"),
        policy = require("js/actions/policy"),
        shortcuts = require("js/util/shortcuts"),
        SuperselectOverlay = require("jsx!js/jsx/tools/SuperselectOverlay"),
        EventPolicy = require("js/models/eventpolicy"),
        PolicyStore = require("js/stores/policy"),
        KeyboardEventPolicy = EventPolicy.KeyboardEventPolicy,
        PointerEventPolicy = EventPolicy.PointerEventPolicy;

    /**
     * Flag to keep track of space key, so we can start panning on empty pixels
     */
    var _spaceKeyDown;

    /**
     * @private
     */
    var _selectHandler = function () {
        var toolOptions = {
                "$AtSl": false, // Don't auto select on drag
                "$ASGr": false, // Don't auto select Groups,
                "$Abbx": true // Show transform controls
            };

        var toolStore = this.flux.store("tool"),
            applicationStore = this.flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument(),
            vectorMode = toolStore.getVectorMode();

        if (!vectorMode || !currentDocument) {
            return descriptor.playObject(toolLib.setToolOptions("moveTool", toolOptions))
                .bind(this)
                .then(function () {
                    return this.transfer(policy.setMode, PolicyStore.eventKind.POINTER,
                        UI.pointerPropagationMode.ALPHA_PROPAGATE_WITH_NOTIFY);
                });
        } else {
            var currentLayers = currentDocument.layers.selected;

            if (currentLayers.isEmpty()) {
                return Promise.resolve();
            }

            var currentLayer = currentLayers.first();

            if (currentLayer.vectorMaskEnabled) {
                return this.transfer(layerActions.resetLayers, currentDocument, currentLayer)
                    .bind(this)
                    .then(function () {
                        currentLayer = applicationStore.getCurrentDocument().layers.selected.first();
                        if (!currentLayer.vectorMaskEmpty) {
                            return descriptor.playObject(vectorMaskLib.activateVectorMaskEditing())
                                .bind(this)
                                .then(function () {
                                    // We are not transfering here
                                    // because we activly want to end the use of our locks
                                    this.flux.actions.tools.enterPathModalState();
                                });
                        }
                    });
            }
        }
    };

    /**
     * @private
     */
    var _deselectHandler = function () {
        return this.transfer(policy.setMode, PolicyStore.eventKind.POINTER,
            UI.pointerPropagationMode.ALPHA_PROPAGATE);
    };

    /**
     * @private
     */
    var _nativeToolName = function () {
        var toolStore = this.flux.store("tool"),
            vectorMode = toolStore.getVectorMode();

        if (vectorMode) {
            return "directSelectTool";
        } else {
            return "moveTool";
        }
    };

    /**
     * Vector Mode requires that Design Space does not swallow pointer commands
     * @private
     * @return {Array.<PointerEventPolicy>}
     */
    var _pointerPolicyList = function () {
        var toolStore = this.flux.store("tool"),
            vectorMode = toolStore.getVectorMode(),
            pointerPolicy;

        if (vectorMode) {
            pointerPolicy = new PointerEventPolicy(UI.policyAction.ALPHA_PROPAGATE,
                OS.eventKind.LEFT_MOUSE_DOWN);
        } else {
            pointerPolicy = new PointerEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.LEFT_MOUSE_DOWN);
        }
        return [pointerPolicy];
    };

    /**
     * @implements {Tool}
     * @constructor
     */
    var SuperSelectTool = function () {
        this.id = "newSelect";
        this.icon = "newSelect";
        this.name = "Super Select";
        this.nativeToolName = _nativeToolName;
        this.dragging = false;
        this.dragEvent = null;
        this.activationKey = shortcuts.GLOBAL.TOOLS.SELECT;
        this.handleVectorMaskMode = true;

        this.selectHandler = _selectHandler;
        this.deselectHandler = _deselectHandler;

        var escapeKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.ESCAPE),
            tabKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.TAB),
            deleteKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.DELETE),
            backspaceKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.BACKSPACE),
            enterKeyPolicy = new KeyboardEventPolicy(UI.policyAction.NEVER_PROPAGATE,
                OS.eventKind.KEY_DOWN, null, OS.eventKeyCode.ENTER);

        this.keyboardPolicyList = [
            escapeKeyPolicy,
            tabKeyPolicy,
            deleteKeyPolicy,
            backspaceKeyPolicy,
            enterKeyPolicy
        ];

        this.pointerPolicyList = _pointerPolicyList;

        _spaceKeyDown = false;

        this.subToolList = [
            new VectorTool(),
            new TypeTool()
        ];
    };
    util.inherits(SuperSelectTool, Tool);

    /**
     * Handler for mouse down events, installed when the tool is active.
     *
     * @param {SyntheticEvent} event
     */
    SuperSelectTool.prototype.onMouseDown = function (event) {
        // We don't want to handle right or middle clicks!
        if (event.button !== 0) {
            return;
        }

        this.dragging = true;
        this.dragEvent = _.clone(event);
    };

    /**
     * Handler for mouse up, turns off dragging
     *
     * @param {SyntheticEvent} event
     */
    SuperSelectTool.prototype.onMouseUp = function (event) {
        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument(),
            mouseDownFlag = !!this.dragEvent,
            diveIn = false;

        if (mouseDownFlag) {
            diveIn = system.isMac ? this.dragEvent.metaKey : this.dragEvent.ctrlKey;
        }

        // Clean up even if we're canceling out
        this.dragging = false;
        this.dragEvent = null;

        // if dragEvent is null, mouse down was not hit, so we shouldn't try to click
        if (!currentDocument || !mouseDownFlag) {
            return;
        }
        flux.actions.superselect.click(currentDocument, event.pageX, event.pageY, diveIn, event.shiftKey);
    };

    /**
     * Handler for mouse move, sends a click to Photoshop at mouse down location
     */
    SuperSelectTool.prototype.onMouseMove = function () {
        if (!this.dragging) {
            return;
        }
        this.dragging = false;

        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument();

        if (!currentDocument) {
            return;
        }

        var dragEvent = this.dragEvent,
            modifiers = {
                option: dragEvent.altKey,
                command: dragEvent.metaKey,
                shift: dragEvent.shiftKey,
                control: dragEvent.ctrlKey
            };

        // Since we don't get the mouse up after we start dragging in PS, we need to reset the event here
        this.dragEvent = null;
        flux.actions.superselect.drag(currentDocument, dragEvent.pageX, dragEvent.pageY, modifiers, _spaceKeyDown);

        // If we start panning, PS is going to eat up the key up event during, so set it to false here
        _spaceKeyDown = false;
    };

    /** @ignore */
    SuperSelectTool.prototype.onClick = function (event) {
        // Prevents clicks from reaching the window and dismissing onWindowClick dialogs
        event.stopPropagation();
    };

    /**
     * Handler for mouse click events, installed when the tool is active.
     *
     * @param {SyntheticEvent} event
     */
    SuperSelectTool.prototype.onDoubleClick = function (event) {
        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            currentDocument = applicationStore.getCurrentDocument();

        if (!currentDocument) {
            return;
        }

        flux.actions.superselect.doubleClick(currentDocument, event.pageX, event.pageY);
    };

    /**
     * Handler for keydown events, installed when the tool is active.
     *
     * TODO Fix this after keyboard policies are more in place
     * @param {CustomEvent} event
     */
    SuperSelectTool.prototype.onKeyDown = function (event) {
        var flux = this.getFlux(),
            applicationStore = flux.store("application"),
            toolStore = flux.store("tool"),
            vectorMaskMode = toolStore.getVectorMode(),
            currentDocument = applicationStore.getCurrentDocument();

        if (!currentDocument) {
            return;
        }

        var detail = event.detail;

        switch (detail.keyCode) {
        case OS.eventKeyCode.ESCAPE: // Escape
            if (vectorMaskMode) {
                flux.actions.tools.changeVectorMaskMode(false);
            } else {
                var dontDeselectAll = system.isMac ? detail.modifiers.alt : detail.modifiers.shift;
                flux.actions.superselect.backOut(currentDocument, dontDeselectAll);
            }
            break;
        case OS.eventKeyCode.TAB: // Tab
            var cycleBack = detail.modifiers.shift;
            flux.actions.superselect.nextSibling(currentDocument, cycleBack);
            break;
        case OS.eventKeyCode.ENTER: // Enter
            if (vectorMaskMode) {
                adapterPS.endModalToolState(true);
            } else {
                flux.actions.superselect.diveIn(currentDocument);
            }
            break;
        case OS.eventKeyCode.DELETE: // DELETE
        case OS.eventKeyCode.BACKSPACE: // BACKSPACE
            if (toolStore.getVectorMode()) {
                flux.actions.layers.deleteVectorMask()
                    .then(function () {
                        flux.actions.tools.changeVectorMaskMode(false);
                    });
            }
        }

        if (detail.keyChar === " ") {
            _spaceKeyDown = true;
        }
    };

    /**
     * Handler for key up events, we look for space bar key up event to
     * reset the flag we send to superselect.drag
     *
     * @param {CustomEvent} event
     */
    SuperSelectTool.prototype.onKeyUp = function (event) {
        var detail = event.detail;

        if (detail.keyChar === " ") {
            _spaceKeyDown = false;
        }
    };

    SuperSelectTool.prototype.toolOverlay = SuperselectOverlay;

    module.exports = SuperSelectTool;
});
<|endoftext|>


<commit_before>
/*jslint browser: true, node: true */
/*global $, PAPAYA_MENU_UNSELECTABLE, PAPAYA_MENU_SLIDER, PAPAYA_MENU_HOVERING_CSS */

"use strict";

/*** Imports ***/
var papaya = papaya || {};
papaya.ui = papaya.ui || {};

/*** Constructor ***/
papaya.ui.MenuItemSlider = papaya.ui.MenuItemSlider || function (viewer, label, action, callback, dataSource, method,
                                                                 modifier) {
    if (action === "alphaneg") {
        action = "alpha";
        modifier = viewer.getScreenVolumeIndex(viewer.screenVolumes[parseInt(modifier)].negativeScreenVol).toString();
    }

    this.viewer = viewer;
    this.label = label;

    this.index = modifier;
    this.modifier = "";
    if (!papaya.utilities.StringUtils.isStringBlank(modifier)) {
        this.modifier = "-" + modifier;
    }

    this.dataSource = dataSource;
    this.method = method;
    this.action = action;
    this.id = this.action.replace(/ /g, "_") + this.viewer.container.containerIndex;
    this.callback = callback;
    this.screenVol = this.viewer.screenVolumes[this.index];
};

/*** Prototype Methods ***/

papaya.ui.MenuItemSlider.prototype.buildHTML = function (parentId) {
    var html, thisHtml, sliderId, sliderHtml, menuItem;

    sliderId = this.id + "Slider";

    html = "
" +
        this.label + ":<input min='0' max='100' value='" + parseInt((1.0 - this.screenVol.alpha) * 100, 10) +
        "' id='" + sliderId + "' class='" + PAPAYA_MENU_SLIDER + "' type='range' />
";
    $("#" + parentId).append(html);

    thisHtml = $("#" + this.id);
    thisHtml.hover(function () { $(this).toggleClass(PAPAYA_MENU_HOVERING_CSS); });
    sliderHtml = $("#" + sliderId);

    menuItem = this;

    $("#" + this.id + "Slider").change(function () {
        menuItem.screenVol[menuItem.action] = 1.0 - (sliderHtml.val() / 100.0);
        menuItem.viewer.drawViewer(true, false);
    });
};

papaya.ui.MenuItemSlider.prototype.doAction = function () {
    this.callback(this.action);
};
<commit_msg>minor changes to better support context manager<commit_after>
/*jslint browser: true, node: true */
/*global $, PAPAYA_MENU_UNSELECTABLE, PAPAYA_MENU_SLIDER, PAPAYA_MENU_HOVERING_CSS */

"use strict";

/*** Imports ***/
var papaya = papaya || {};
papaya.ui = papaya.ui || {};

/*** Constructor ***/
papaya.ui.MenuItemSlider = papaya.ui.MenuItemSlider || function (viewer, label, action, callback, dataSource, method,
                                                                 modifier) {
    if (action === "alphaneg") {
        action = "alpha";
        modifier = viewer.getScreenVolumeIndex(viewer.screenVolumes[parseInt(modifier)].negativeScreenVol).toString();
    }

    this.viewer = viewer;
    this.label = label;

    this.index = modifier;
    this.modifier = "";
    if (!papaya.utilities.StringUtils.isStringBlank(modifier)) {
        this.modifier = "-" + modifier;
    }

    this.dataSource = dataSource;
    this.method = method;
    this.action = action;
    this.id = this.action.replace(/ /g, "_") + this.viewer.container.containerIndex + "_" + this.index;
    this.callback = callback;
    this.screenVol = this.viewer.screenVolumes[this.index];
};

/*** Prototype Methods ***/

papaya.ui.MenuItemSlider.prototype.buildHTML = function (parentId) {
    var html, thisHtml, sliderId, sliderHtml, menuItem;

    sliderId = this.id + "Slider";

    html = "
" +
        this.label + ":<input min='0' max='100' value='" + parseInt((1.0 - this.screenVol.alpha) * 100, 10) +
        "' id='" + sliderId + "' class='" + PAPAYA_MENU_SLIDER + "' type='range' />
";
    $("#" + parentId).append(html);

    thisHtml = $("#" + this.id);
    thisHtml.hover(function () { $(this).toggleClass(PAPAYA_MENU_HOVERING_CSS); });
    sliderHtml = $("#" + sliderId);

    menuItem = this;

    $("#" + this.id + "Slider").change(function () {
        menuItem.screenVol[menuItem.action] = 1.0 - (sliderHtml.val() / 100.0);
        menuItem.viewer.drawViewer(true, false);
    });
};

papaya.ui.MenuItemSlider.prototype.doAction = function () {
    this.callback(this.action);
};
<|endoftext|>


<commit_before>import { EventEmitter } from '../components/event-emitter';

/**
 * This modal loads mapzen.com's login flow into an iframe, so it outsources
 * as much of it as possible out of Tangram Play. When the iframe source looks
 * like the user has successfully signed in / signed up, we toggle the state
 * on the login window and the user log in state in the application.
 * Due to web security restrictions, this must be tested on mapzen.com and
 * should not be loaded in other contexts (e.g. localhost)
 */

let loginWindow;
let pollWindowStateIntervalId;
let redirectingTimerId;

/**
 * Primary entry point for opening a login window.
 */
export function openLoginWindow () {
    // Only open if not open already; or was closed from a previous attempt.
    // If it's already open, focus on that instead.
    if (!loginWindow || loginWindow.closed === true) {
        loginWindow = popupCenter('/developers/sign_in', 'Login to Mapzen Developer Portal', 650, 650);
        loginWindow.addEventListener('close', cleanup);
        window.addEventListener('unload', closeLoginWindow);

        // Experimental.
        loginWindow.document.body.style.opacity = 0;
        loginWindow.addEventListener('load', adjustLoginPageContent);

        // We can't add load or close event listeners to the new window (they
        // don't trigger) so instead we poll the window at a set interval
        // and perform actions based on what we can detect inside of it.
        pollWindowStateIntervalId = window.setInterval(pollWindowState, 100);
    }

    // This new window should grab the user's attention immediately
    // Apparently, this doesn't work in all browsers (e.g. Chrome) due to
    // security policies.
    loginWindow.focus();
}

function pollWindowState () {
    if (!loginWindow || loginWindow.closed) {
        window.clearInterval(pollWindowStateIntervalId);
        loginStateReady();
    }
    else {
        try {
            // If it's exactly /developers, we're probably logged in now
            if (loginWindow.location.pathname === '/developers') {
                adjustDonePageContent();
                if (!redirectingTimerId) {
                    redirectingTimerId = window.setTimeout(loginStateReady, 2000);
                }
            }
            // Experimental: hack the view for developer pages.
            if (loginWindow.location.pathname.indexOf('/developers/') === 0) {
                adjustLoginPageContent();
            }
        }
        catch (e) {
            // Probably a security policy in the way; ignore
        }
    }
}

/**
 * Called when user completes the flow in the login window.
 */
function loginStateReady () {
    EventEmitter.dispatch('mapzen:sign_in', {});
    closeLoginWindow();

    // Returns focus to the original parent window.
    window.focus();
}

/**
 * Closes the login window. This is called when we detect that the user
 * completes the login flow, and if the app window is closed.
 * Closing the login window should automatically clean up after itself
 * due to the `close` event handler.
 */
function closeLoginWindow () {
    if (loginWindow) {
        loginWindow.close();
    }
}

/**
 * Cleans up event listeners from the app window to prevent memory leaks.
 */
function cleanup () {
    window.removeEventListener('unload', closeLoginWindow);
    window.clearInterval(pollWindowStateIntervalId);
    window.clearTimeout(redirectingTimerId);
}

/**
 * A super hacky experiment to restyle Mapzen's stock login page just the way
 * we like it. There's no guarantee this works forever especially as DOM
 * elements, class selectors and styles may evolve over time.
 */
function adjustLoginPageContent () {
    const childDocument = loginWindow.document;

    // Only do this once
    if (childDocument.querySelector('#dev_login') && !childDocument.getElementById('tangram-play-login-override-styles')) {
        const newStyleEl = document.createElement('style');
        const newStyleText = `
            body {
                margin-top: 0;
                overflow: hidden;
            }
            h3 {
                line-height: 1.4em;
                margin-bottom: 20px;
            }
            body.hide-fixed-main-nav nav.navbar-fixed-top {
                top: 0;
            }
            nav.navbar.navbar-default.navbar-fixed-top {
                position: absolute;
            }
            .navbar-collapse.navbar-collapse.navbar-collapse {
                display: none !important;
            }
            a.navbar-brand {
                left: 50%;
                position: absolute;
                margin-left: -80px !important;
                pointer-events: none;
                user-select: none;
                touch-action: none;
            }
            .headroom-extra-large {
                margin-top: 20px;
            }
            footer {
                display: none;
            }
            #dev_login.container {
                position: absolute;
                bottom: 0;
                top: 60px;
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
                left: 0;
                width: 100%;
                max-width: 100%;
                padding-left: 3em;
                padding-right: 3em;
            }
            @media (max-height: 500px) {
                #dev_login.container {
                    top: 50px;
                    transform: scale(.80);
                    padding-left: 1em;
                    padding-right: 1em;
                }
            }
            button.navbar-toggle {
                display: none;
            }
        `;
        newStyleEl.id = 'tangram-play-login-override-styles';
        newStyleEl.textContent = newStyleText;

        childDocument.head.appendChild(newStyleEl);
        childDocument.querySelector('#dev_login h1').textContent = 'Sign in to Mapzen';
        childDocument.querySelector('#dev_login h3').textContent = 'You can save Tangram scenes to your Mapzen account and do other stuff good too';
    }
}

function adjustDonePageContent () {
    const childDocument = loginWindow.document;

    // Only do this once
    if (!childDocument.getElementById('tangram-play-login-override-styles')) {
        const newStyleEl = document.createElement('style');
        const newStyleText = `
            body {
                margin-top: 0;
                overflow: hidden;
            }
            body.hide-fixed-main-nav nav.navbar-fixed-top {
                top: 0;
            }
            nav.navbar.navbar-default.navbar-fixed-top {
                position: absolute;
            }
            .navbar-collapse.navbar-collapse.navbar-collapse {
                display: none !important;
            }
            a.navbar-brand {
                left: 50%;
                position: absolute;
                margin-left: -80px !important;
                pointer-events: none;
                user-select: none;
                touch-action: none;
            }
            button.navbar-toggle {
                display: none;
            }
            #tangram-play-override-redirecting {
                position: fixed;
                top: 0px;
                left: 0px;
                width: 100%;
                height: 100%;
                background-color: white;
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
            }
        `;
        newStyleEl.id = 'tangram-play-login-override-styles';
        newStyleEl.textContent = newStyleText;

        childDocument.head.appendChild(newStyleEl);

        const redirectEl = document.createElement('div');
        redirectEl.id = 'tangram-play-override-redirecting';

        const loadingSpinnerEl = document.createElement('div');
        loadingSpinnerEl.className = 'loading-spinner-02';

        const textEl = document.createTextNode('p');
        textEl.textContent = 'Signed in! Redirecting...';

        redirectEl.appendChild(loadingSpinnerEl);
        redirectEl.appendChild(textEl);
        childDocument.body.appendChild(redirectEl);
    }
}

/**
 * Opens a new window for the login page and places it in the middle of the
 * app window. This was taken from a StackOverflow answer that we've repurposed
 * several times through mapzen.com but still has a problem with some browsers
 * and some multi-monitor setups.
 */
function popupCenter (url, title, w, h) {
    // Fixes dual-screen position                            Most browsers       Firefox
    const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screen.left;
    const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screen.top;

    const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : window.screen.width;
    const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : window.screen.height;

    const left = ((width / 2) - (w / 2)) + dualScreenLeft;
    const top = ((height / 3) - (h / 3)) + dualScreenTop;

    return window.open(url, title, 'scrollbars=yes, location=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
}
<commit_msg>Login: done-state window needs a window.close() escape valve<commit_after>import { EventEmitter } from '../components/event-emitter';

/**
 * This modal loads mapzen.com's login flow into an iframe, so it outsources
 * as much of it as possible out of Tangram Play. When the iframe source looks
 * like the user has successfully signed in / signed up, we toggle the state
 * on the login window and the user log in state in the application.
 * Due to web security restrictions, this must be tested on mapzen.com and
 * should not be loaded in other contexts (e.g. localhost)
 */

let loginWindow;
let pollWindowStateIntervalId;
let redirectingTimerId;

/**
 * Primary entry point for opening a login window.
 */
export function openLoginWindow () {
    // Only open if not open already; or was closed from a previous attempt.
    // If it's already open, focus on that instead.
    if (!loginWindow || loginWindow.closed === true) {
        loginWindow = popupCenter('/developers/sign_in', 'Login to Mapzen Developer Portal', 650, 650);
        loginWindow.addEventListener('close', cleanup);
        window.addEventListener('unload', closeLoginWindow);

        // Experimental.
        loginWindow.document.body.style.opacity = 0;
        loginWindow.addEventListener('load', adjustLoginPageContent);

        // We can't add load or close event listeners to the new window (they
        // don't trigger) so instead we poll the window at a set interval
        // and perform actions based on what we can detect inside of it.
        pollWindowStateIntervalId = window.setInterval(pollWindowState, 100);
    }

    // This new window should grab the user's attention immediately
    // Apparently, this doesn't work in all browsers (e.g. Chrome) due to
    // security policies.
    loginWindow.focus();
}

function pollWindowState () {
    if (!loginWindow || loginWindow.closed) {
        window.clearInterval(pollWindowStateIntervalId);
        loginStateReady();
    }
    else {
        try {
            // If it's exactly /developers, we're probably logged in now
            if (loginWindow.location.pathname === '/developers') {
                adjustDonePageContent();
                if (!redirectingTimerId) {
                    redirectingTimerId = window.setTimeout(loginStateReady, 2000);
                }
            }
            // Experimental: hack the view for developer pages.
            if (loginWindow.location.pathname.indexOf('/developers/') === 0) {
                adjustLoginPageContent();
            }
        }
        catch (e) {
            // Probably a security policy in the way; ignore
        }
    }
}

/**
 * Called when user completes the flow in the login window.
 */
function loginStateReady () {
    EventEmitter.dispatch('mapzen:sign_in', {});
    closeLoginWindow();

    // Returns focus to the original parent window.
    window.focus();
}

/**
 * Closes the login window. This is called when we detect that the user
 * completes the login flow, and if the app window is closed.
 * Closing the login window should automatically clean up after itself
 * due to the `close` event handler.
 */
function closeLoginWindow () {
    if (loginWindow) {
        loginWindow.close();
    }
}

/**
 * Cleans up event listeners from the app window to prevent memory leaks.
 */
function cleanup () {
    window.removeEventListener('unload', closeLoginWindow);
    window.clearInterval(pollWindowStateIntervalId);
    window.clearTimeout(redirectingTimerId);
}

/**
 * A super hacky experiment to restyle Mapzen's stock login page just the way
 * we like it. There's no guarantee this works forever especially as DOM
 * elements, class selectors and styles may evolve over time.
 */
function adjustLoginPageContent () {
    const childDocument = loginWindow.document;

    // Only do this once
    if (childDocument.querySelector('#dev_login') && !childDocument.getElementById('tangram-play-login-override-styles')) {
        const newStyleEl = document.createElement('style');
        const newStyleText = `
            body {
                margin-top: 0;
                overflow: hidden;
            }
            h3 {
                line-height: 1.4em;
                margin-bottom: 20px;
            }
            body.hide-fixed-main-nav nav.navbar-fixed-top {
                top: 0;
            }
            nav.navbar.navbar-default.navbar-fixed-top {
                position: absolute;
            }
            .navbar-collapse.navbar-collapse.navbar-collapse {
                display: none !important;
            }
            a.navbar-brand {
                left: 50%;
                position: absolute;
                margin-left: -80px !important;
                pointer-events: none;
                user-select: none;
                touch-action: none;
            }
            .headroom-extra-large {
                margin-top: 20px;
            }
            footer {
                display: none;
            }
            #dev_login.container {
                position: absolute;
                bottom: 0;
                top: 60px;
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
                left: 0;
                width: 100%;
                max-width: 100%;
                padding-left: 3em;
                padding-right: 3em;
            }
            @media (max-height: 500px) {
                #dev_login.container {
                    top: 50px;
                    transform: scale(.80);
                    padding-left: 1em;
                    padding-right: 1em;
                }
            }
            button.navbar-toggle {
                display: none;
            }
        `;
        newStyleEl.id = 'tangram-play-login-override-styles';
        newStyleEl.textContent = newStyleText;

        childDocument.head.appendChild(newStyleEl);
        childDocument.querySelector('#dev_login h1').textContent = 'Sign in to Mapzen';
        childDocument.querySelector('#dev_login h3').textContent = 'You can save Tangram scenes to your Mapzen account and do other stuff good too';
    }
}

function adjustDonePageContent () {
    const childDocument = loginWindow.document;

    // Only do this once
    if (!childDocument.getElementById('tangram-play-login-override-styles')) {
        const newStyleEl = document.createElement('style');
        const newStyleText = `
            body {
                margin-top: 0;
                overflow: hidden;
            }
            body.hide-fixed-main-nav nav.navbar-fixed-top {
                top: 0;
            }
            nav.navbar.navbar-default.navbar-fixed-top {
                position: absolute;
            }
            .navbar-collapse.navbar-collapse.navbar-collapse {
                display: none !important;
            }
            a.navbar-brand {
                left: 50%;
                position: absolute;
                margin-left: -80px !important;
                pointer-events: none;
                user-select: none;
                touch-action: none;
            }
            button.navbar-toggle {
                display: none;
            }
            #tangram-play-override-redirecting {
                position: fixed;
                top: 0px;
                left: 0px;
                width: 100%;
                height: 100%;
                background-color: white;
                display: flex;
                flex-direction: column;
                justify-content: center;
                align-items: center;
            }
        `;
        newStyleEl.id = 'tangram-play-login-override-styles';
        newStyleEl.textContent = newStyleText;

        childDocument.head.appendChild(newStyleEl);

        const redirectEl = document.createElement('div');
        redirectEl.id = 'tangram-play-override-redirecting';

        const loadingSpinnerEl = document.createElement('div');
        loadingSpinnerEl.className = 'loading-spinner-02';
        loadingSpinnerEl.style.marginBottom = '10px';

        const textEl = document.createElement('p');
        textEl.textContent = 'Signed in! Redirecting...';

        // It is safe to close this window, but if the window doesn't close
        // automatically for whatever reason, this link will appear
        const closeEl = document.createElement('p');
        closeEl.innerHTML = 'Click here to close this window.';
        closeEl.style.opacity = '0';
        closeEl.style.userSelect = 'none';

        redirectEl.appendChild(loadingSpinnerEl);
        redirectEl.appendChild(textEl);
        redirectEl.appendChild(closeEl);
        childDocument.body.appendChild(redirectEl);

        // If window doesn't close automatically for some reason, activate this link
        window.setTimeout(function () {
            closeEl.style.opacity = '1';
            closeEl.style.userSelect = 'auto';
        }, 2000);
    }
}

/**
 * Opens a new window for the login page and places it in the middle of the
 * app window. This was taken from a StackOverflow answer that we've repurposed
 * several times through mapzen.com but still has a problem with some browsers
 * and some multi-monitor setups.
 */
function popupCenter (url, title, w, h) {
    // Fixes dual-screen position                            Most browsers       Firefox
    const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screen.left;
    const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screen.top;

    const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : window.screen.width;
    const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : window.screen.height;

    const left = ((width / 2) - (w / 2)) + dualScreenLeft;
    const top = ((height / 3) - (h / 3)) + dualScreenTop;

    return window.open(url, title, 'scrollbars=yes, location=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
}
<|endoftext|>