<commit_before>"use strict";
window.gokart = (function(self) {
    $(document).foundation();
    svg4everybody();

    var $self = $(self)
    var ui = self.ui = {};

    // bind menu side-tabs to reveal the side pane
    var offCanvasLeft = $("#offCanvasLeft");
    $("#menu-tabs").on("change.zf.tabs", function(ev) {
        offCanvasLeft.addClass("reveal-responsive");
        self.map.updateSize();
    }).on("click", ".tabs-title a[aria-selected=false]", function(ev) {
        offCanvasLeft.addClass("reveal-responsive");
        $(this).attr("aria-selected", true);
        self.map.updateSize();
    }).on("click", ".tabs-title a[aria-selected=true]", function(ev) {
        offCanvasLeft.removeClass("reveal-responsive");
        $(this).attr("aria-selected", false);
        self.map.updateSize();
    });
    $("#side-pane-close").on("click", function(ev) {
        offCanvasLeft.removeClass("reveal-responsive");
        $("#menu-tabs").find(".tabs-title a[aria-selected=true]").attr("aria-selected", false);
        self.map.updateSize();
    });

    // hovering layer information panel
    Vue.partial('featureInfo', document.querySelectorAll("#featureInfo")[0].innerHTML);
    ui.info = new Vue({
        el: '#info',
        // variables
        data: {
            enabled: true,
            features: [],
            coordinate: "",
            sel: [],
            offset: 20,
            pixel: [0, 0],
        },
        // parts of the template to be computed live
        computed: {
            // because the viewport size changes when the tab pane opens, don't cache the map width and height
            mapWidth: { cache: false, get: function() { return $("#map").width() }},
            mapHeight: { cache: false, get: function() { return $("#map").height() }},
            featuresLength: function() {
                return Object.keys(this.features).length;
            },
            // info panel should be positioned near the mouse in the quadrant furthest away from the viewport edges
            css: function() {
                var css = {
                    "left": this.pixel[0] + this.offset + "px",
                    "top": this.pixel[1] + this.offset + "px",
                    "bottom": this.mapHeight - this.pixel[1] + this.offset + "px",
                    "right": this.mapWidth - this.pixel[0] + this.offset + "px"
                }
                if (this.pixel[0] < this.mapWidth / 2) { delete css.right } else { delete css.left };
                if (this.pixel[1] < this.mapHeight / 2) { delete css.bottom } else { delete css.top };
                return css;
            }
        },
        // methods callable from inside the template
        methods: {
            // update the panel content
            display: function(event) {
                if (event.dragging) { return };
                var pixel = self.map.getEventPixel(event.originalEvent);
                var features = [];
                var featureFound = self.map.forEachFeatureAtPixel(pixel, function(f) {
                    features.push(f);
                });
                if (Object.keys(features).length > 0) {
                    this.features = features;
                    this.coordinate = ol.coordinate.toStringXY(self.map.getCoordinateFromPixel(pixel), 3);
                    this.pixel = pixel;
                }
            },
            onPointerMove: function(event) {
                if (this.enabled) {
                    this.display(event);
                }
            },
            selected: function(f) {
                var id = f.get("id") || f.getId();
                return this.sel.indexOf(id) > -1;
            },
            select: function(f) {
                var id = f.get("id") || f.getId();
                if (this.sel.indexOf(id) > -1) {
                    this.sel.$remove(id);
                } else {
                    this.sel.push(id);
                }
            }
        }
    });

    // update "Map Layers" pane
    self.initLayers = function() {
        ui.layers = new Vue({
            el: "#layers-active",
            // variables
            data: {
                sliderOpacity: 0,
                layer: {},
                olLayers: self.map.getLayers().getArray(),
                hoverInfoCache: true,
                timeIndex: 0
            },
            // parts of the template to be computed live
            computed: {
                graticule: {
                    cache: false,
                    get: function() {
                        return self.graticule.getMap() == self.map;
                    }
                },
                hoverInfo: {
                    cache: false,
                    get: function() {
                        return ui.info.enabled;
                    },
                    set: function(val) {
                        ui.info.enabled = val;
                    }
                },
                sliderTimeline: {
                    get: function() {
                        this.timeIndex = this.layer.olLayer().get("timeIndex");
                        return this.timeIndex;
                    },
                    set: function(val) {
                        this.layer.olLayer().set("timeIndex", val);
                        this.timeIndex = val;
                    }
                },
                timelineTS: {
                    cache: false,
                    get: function() {
                        return this.layer.timeline[this.timeIndex][0];
                    }
                },
                sliderMax: {
                    cache: false,
                    get: function() {
                        return this.layer.timeline.length-1;
                    }
                },
                layerOpacity: {
                    get: function() {
                        return Math.round(this.layer.olLayer().getOpacity() * 100);
                    },
                    set: function(val) {
                        this.layer.olLayer().setOpacity(val / 100);
                    }
                }
            },
            // methods callable from inside the template
            methods: {
                getLayer: self.getLayer,
                toggleGraticule: function() {
                    if (this.graticule) {
                        self.graticule.setMap(null);
                    } else {
                        self.graticule.setMap(self.map);
                    }
                },
                toggleHoverInfo: function(ev) {
                    this.hoverInfoCache = ev.target.checked;
                    this.hoverInfo = ev.target.checked;
                },
                update: function() {
                    var vm = this;
                    vm.olLayers = [];
                    Vue.nextTick(function() {
                        vm.olLayers = gokart.map.getLayers().getArray();
                    });
                },
                removeLayer: function(olLayer) {
                    self.map.removeLayer(olLayer);
                },
                // change order of OL layers based on "Map Layers" list order
                updateOrder: function(el) {
                    Array.prototype.slice.call(el.parentNode.children).reverse().forEach(function(row) {
                        var layer = self.getMapLayer(row.dataset.id);
                        self.map.removeLayer(layer);
                        self.map.addLayer(layer);
                    });
                },
            },
            ready: function () {
                dragula([document.querySelector("#layers-active-list")]).on("dragend", this.updateOrder);
            }
        });
        ui.catalogue = new Vue({
            el: "#layers-catalogue",
            data: {
                layer: {},
                catalogue: self.catalogue,
                swapBaseLayers: true,
                search: "",
                searchAttrs: ["name", "id"]
            },
            methods: {
                // helper function to simulate a <label> style click on a row
                onToggle: function(index) {
                    $(this.$el).find("#ctlgsw"+index).trigger("click");
                },
                // toggle a layer in the Layer Catalogue
                onLayerChange: function(layer, checked) {
                    // if layer matches state, return
                    if (checked == (layer.olLayer() !== undefined)) { return }
                    // make the layer match the state
                    if (checked) {
                        if (layer.base) {
                            // "Switch out base layers automatically" is enabled, remove
                            // all other layers with the "base" option set.
                            if (this.swapBaseLayers) {
                                ui.layers.olLayers.forEach(function(olLayer) {
                                    if (self.getLayer(olLayer.get("id")).base) {
                                        ui.layers.removeLayer(olLayer);
                                    }
                                });
                            }
                            // add new base layer to bottom
                            self.map.getLayers().insertAt(0, layer.init());
                        } else {
                            self.map.addLayer(layer.init());
                        }
                    } else {
                        ui.layers.removeLayer(layer.olLayer());
                    }
                }
            }
        });
    };

    // collection to store all annotation features
    ui.features = new ol.Collection();
    ui.selectedFeatures = new ol.Collection();
    ui.features.on("add", function(ev) {
        var feature = ev.element;
        if (feature.get("toolName")) {
            var style = ui.annotations.tools.filter(function(t) {
                return t.name == feature.get("toolName");
            })[0].style;
        } else {
            feature.set("toolName", ui.annotations.tool.name);
            var style = ui.annotations.tool.style;
        }
        feature.setStyle(style || null);
    });
    // NASTYHACK: add/remove default style based on select status
    ui.selectedFeatures.on("add", function(ev) {
        var feature = ev.element;
        feature.preSelectStyle = feature.getStyle();
        feature.setStyle(null);
    });
    ui.selectedFeatures.on("remove", function(ev) {
        var feature = ev.element;
        feature.setStyle(feature.preSelectStyle);
        delete feature.preSelectStyle;
    });
    // layer/source for modiftying annotation features
    ui.featureOverlay = new ol.layer.Vector({
        source: new ol.source.Vector({ features: ui.features })
    });
    ui.featureOverlay.set("id", "annotations");
    ui.featureOverlay.set("name", "My Annotations");
    // collection for tracking selected features

    // add new points to annotations layer
    ui.pointInter = new ol.interaction.Draw({
        type: "Point",
        features: ui.features
    });

    // add new lines to annotations layer
    ui.lineInter = new ol.interaction.Draw({
        type: "LineString",
        features: ui.features
    });

    // add new polygons to annotations layer
    ui.polyInter = new ol.interaction.Draw({
        type: "Polygon",
        features: ui.features
    });

    // next three interacts are bundled into the Select tool
    // allow modifying features by click+dragging
    ui.modifyInter = new ol.interaction.Modify({
        features: ui.features
    });

    // allow dragbox selection of features
    ui.dragSelectInter = new ol.interaction.DragBox();
    // modify selectedFeatures after dragging a box
    ui.dragSelectInter.on("boxend", function() {
        var extent = ui.dragSelectInter.getGeometry().getExtent();
        ui.featureOverlay.getSource().forEachFeatureIntersectingExtent(extent, function(feature) {
            ui.selectedFeatures.push(feature);
        });
    });
    // clear selectedFeatures before dragging a box
    ui.dragSelectInter.on("boxstart", function() {
        ui.selectedFeatures.clear();
    });
    // allow selecting multiple features by clicking
    ui.selectInter = new ol.interaction.Select({
        layers: [ui.featureOverlay],
        features: ui.selectedFeatures
    });

    self.initAnnotations = function() {
        self.catalogue.push({
            init: function() {
                return ui.featureOverlay;
            },
            id: "annotations",
            name: "My Annotations"
        });
        var defaultTool = {
            name: "Pan",
            icon: "fa-hand-paper-o",
            interactions: [
                self.dragPanInter,
                self.doubleClickZoomInter
            ]
        };
        ui.annotations = new Vue({
            el: "#menu-tab-annotations",
            data: {
                tool: defaultTool,
                tools: [
                    defaultTool,
                    {
                        name: "Select",
                        icon: "fa-mouse-pointer",
                        interactions: [
                            ui.selectInter,
                            ui.dragSelectInter,
                            ui.modifyInter
                        ]
                    },
                    {
                        name: "Point",
                        icon: "/static/images/iD-sprite.svg#icon-point",
                        interactions: [ui.pointInter]
                    },
                    {
                        name: "Line",
                        icon: "/static/images/iD-sprite.svg#icon-line",
                        interactions: [ui.lineInter]
                    },
                    {
                        name: "Polygon",
                        icon: "/static/images/iD-sprite.svg#icon-area",
                        interactions: [ui.polyInter]
                    }
                ],
                features: ui.features,
                featureOverlay: ui.featureOverlay,
                size: 12,
                colour: "#cc0000",
                colours: [
                    ["red", "#cc0000"],
                    ["orange", "#f57900"],
                    ["yellow", "#edd400"],
                    ["green", "#73d216"],
                    ["blue", "#3465a4"],
                    ["violet", "#75507b"],
                    ["brown", "#8f5902"],
                    ["grey", "#555753"],
                    ["black", "#000000"]
                ],
                advanced: false
            },
            methods: {
                icon: function(t) {
                    if (t.icon.startsWith("fa-")) {
                        return '<i class="fa '+t.icon+'" aria-hidden="true"></i>';
                    } else {
                        return '<svg class="icon"><use xlink:href="'+t.icon+'"></use></svg>';
                    }
                },
                setTool: function(t) {
                    var vm = this;
                    vm.tools.forEach(function(tool) {
                        tool.interactions.forEach(function (inter) {
                            self.map.removeInteraction(inter);
                        });
                    });
                    t.interactions.forEach(function (inter) {
                        self.map.addInteraction(inter);
                    });
                    ui.layers.hoverInfo = ((t.name == 'Pan') && (ui.layers.hoverInfoCache));
                    if (!self.getLayer("annotations").olLayer()) {
                        ui.catalogue.onLayerChange(self.getLayer("annotations"), true);
                    }
                    vm.tool = t;
                }
            }
        });
    }

    $self.on("init_map", function() {
        // setup scale events
        self.map.on("postrender", function() {
            if (self.mapScaleControl) {
                self.mapScaleControl.scale = self.getScale();
            }
            if (self.mapExportControls) {
                history.replaceState(null, null, location.pathname + "?" + self.mapExportControls.shortUrl);
            }
        });
        // display hover popups
        self.map.on("pointermove", ui.info.onPointerMove);
        if (document.querySelector("#menu-tab-layers")) { self.initLayers() };
        if (document.querySelector("#menu-tab-annotations")) { self.initAnnotations() };

        $.get("/static/images/legend.svg", function(tmpl) {
            $("#legendsvg").html(tmpl);
            self.initMapControls();
        }, "text");
    });

    return self;
})(window.gokart || {});

<commit_msg>vue fixes<commit_after>"use strict";
window.gokart = (function(self) {
    $(document).foundation();
    svg4everybody();

    var $self = $(self)
    var ui = self.ui = {};

    // bind menu side-tabs to reveal the side pane
    var offCanvasLeft = $("#offCanvasLeft");
    $("#menu-tabs").on("change.zf.tabs", function(ev) {
        offCanvasLeft.addClass("reveal-responsive");
        self.map.updateSize();
    }).on("click", ".tabs-title a[aria-selected=false]", function(ev) {
        offCanvasLeft.addClass("reveal-responsive");
        $(this).attr("aria-selected", true);
        self.map.updateSize();
    }).on("click", ".tabs-title a[aria-selected=true]", function(ev) {
        offCanvasLeft.removeClass("reveal-responsive");
        $(this).attr("aria-selected", false);
        self.map.updateSize();
    });
    $("#side-pane-close").on("click", function(ev) {
        offCanvasLeft.removeClass("reveal-responsive");
        $("#menu-tabs").find(".tabs-title a[aria-selected=true]").attr("aria-selected", false);
        self.map.updateSize();
    });

    // hovering layer information panel
    self.initInfo = function() {
        ui.info = new Vue({
            el: '#info',
            // variables
            data: {
                enabled: true,
                features: [],
                coordinate: "",
                sel: [],
                offset: 20,
                pixel: [0, 0],
            },
            // parts of the template to be computed live
            computed: {
                // because the viewport size changes when the tab pane opens, don't cache the map width and height
                mapWidth: { cache: false, get: function() { return self.map.getTargetElement().clientWidth }},
                mapHeight: { cache: false, get: function() { return self.map.getTargetElement().clientHeight }},
                featuresLength: function() {
                    return Object.keys(this.features).length;
                },
                // info panel should be positioned near the mouse in the quadrant furthest away from the viewport edges
                css: function() {
                    var css = {
                        "left": this.pixel[0] + this.offset + "px",
                        "top": this.pixel[1] + this.offset + "px",
                        "bottom": this.mapHeight - this.pixel[1] + this.offset + "px",
                        "right": this.mapWidth - this.pixel[0] + this.offset + "px",
                        "display": "none"
                    }
                    if (this.pixel[0] < this.mapWidth / 2) { delete css.right } else { delete css.left };
                    if (this.pixel[1] < this.mapHeight / 2) { delete css.bottom } else { delete css.top };
                    if (this.pixel[0] > 0 && this.enabled) { delete css.display };
                    return css;
                }
            },
            // methods callable from inside the template
            methods: {
                // update the panel content
                onPointerMove: function(event) {
                    if (event.dragging || !this.enabled) { return };
                    var pixel = self.map.getEventPixel(event.originalEvent);
                    var features = [];
                    self.map.forEachFeatureAtPixel(pixel, function(f) { features.push(f) });
                    if (features.length > 0) {
                        this.features = features;
                        this.coordinate = ol.coordinate.toStringXY(self.map.getCoordinateFromPixel(pixel), 3);
                        this.pixel = pixel;
                    }
                },
                selected: function(f) {
                    var id = f.get("id") || f.getId();
                    return this.sel.indexOf(id) > -1;
                },
                select: function(f) {
                    var id = f.get("id") || f.getId();
                    if (this.sel.indexOf(id) > -1) {
                        this.sel.$remove(id);
                    } else {
                        this.sel.push(id);
                    }
                }
            },
            beforeCompile: function() {
                Vue.partial('featureInfo', document.querySelectorAll("#featureInfo")[0].innerHTML);
            },
            ready: function() {
                // display hover popups
                self.map.on("pointermove", this.onPointerMove);
            }
        });
    }

    // update "Map Layers" pane
    self.initLayers = function() {
        ui.layers = new Vue({
            el: "#layers-active",
            // variables
            data: {
                sliderOpacity: 0,
                layer: { olLayer: function() {}},
                olLayers: self.map.getLayers().getArray(),
                hoverInfoCache: true,
                timeIndex: 0
            },
            // parts of the template to be computed live
            computed: {
                graticule: {
                    cache: false,
                    get: function() { return self.graticule.getMap() == self.map }
                },
                hoverInfo: {
                    cache: false,
                    get: function() { return ui.info.enabled },
                    set: function(val) { ui.info.enabled = val }
                },
                sliderTimeline: {
                    get: function() {
                        this.timeIndex = this.layer.olLayer().get("timeIndex");
                        return this.timeIndex;
                    },
                    set: function(val) {
                        this.layer.olLayer().set("timeIndex", val);
                        this.timeIndex = val;
                    }
                },
                timelineTS: function() { return this.layer.timeline[this.timeIndex][0] },
                sliderMax: function() { return this.layer.timeline.length - 1 },
                layerOpacity: {
                    get: function() {
                        return Math.round(this.layer.olLayer().getOpacity() * 100);
                    },
                    set: function(val) {
                        this.layer.olLayer().setOpacity(val / 100);
                    }
                }
            },
            // methods callable from inside the template
            methods: {
                getLayer: self.getLayer,
                toggleGraticule: function() {
                    if (this.graticule) {
                        self.graticule.setMap(null);
                    } else {
                        self.graticule.setMap(self.map);
                    }
                },
                toggleHoverInfo: function(ev) {
                    this.hoverInfoCache = ev.target.checked;
                    this.hoverInfo = ev.target.checked;
                },
                update: function() {
                    var vm = this;
                    vm.olLayers = [];
                    Vue.nextTick(function() {
                        vm.olLayers = gokart.map.getLayers().getArray();
                    });
                },
                removeLayer: function(olLayer) {
                    self.map.removeLayer(olLayer);
                },
                // change order of OL layers based on "Map Layers" list order
                updateOrder: function(el) {
                    Array.prototype.slice.call(el.parentNode.children).reverse().forEach(function(row) {
                        var layer = self.getMapLayer(row.dataset.id);
                        self.map.removeLayer(layer);
                        self.map.addLayer(layer);
                    });
                },
            },
            ready: function () {
                dragula([document.querySelector("#layers-active-list")]).on("dragend", this.updateOrder);
            }
        });
        ui.catalogue = new Vue({
            el: "#layers-catalogue",
            data: {
                layer: {},
                catalogue: self.catalogue,
                swapBaseLayers: true,
                search: "",
                searchAttrs: ["name", "id"]
            },
            methods: {
                // helper function to simulate a <label> style click on a row
                onToggle: function(index) {
                    $(this.$el).find("#ctlgsw"+index).trigger("click");
                },
                // toggle a layer in the Layer Catalogue
                onLayerChange: function(layer, checked) {
                    // if layer matches state, return
                    if (checked == (layer.olLayer() !== undefined)) { return }
                    // make the layer match the state
                    if (checked) {
                        if (layer.base) {
                            // "Switch out base layers automatically" is enabled, remove
                            // all other layers with the "base" option set.
                            if (this.swapBaseLayers) {
                                ui.layers.olLayers.forEach(function(olLayer) {
                                    if (self.getLayer(olLayer.get("id")).base) {
                                        ui.layers.removeLayer(olLayer);
                                    }
                                });
                            }
                            // add new base layer to bottom
                            self.map.getLayers().insertAt(0, layer.init());
                        } else {
                            self.map.addLayer(layer.init());
                        }
                    } else {
                        ui.layers.removeLayer(layer.olLayer());
                    }
                }
            }
        });
    };

    // collection to store all annotation features
    ui.features = new ol.Collection();
    ui.selectedFeatures = new ol.Collection();
    ui.features.on("add", function(ev) {
        var feature = ev.element;
        if (feature.get("toolName")) {
            var style = ui.annotations.tools.filter(function(t) {
                return t.name == feature.get("toolName");
            })[0].style;
        } else {
            feature.set("toolName", ui.annotations.tool.name);
            var style = ui.annotations.tool.style;
        }
        feature.setStyle(style || null);
    });
    // NASTYHACK: add/remove default style based on select status
    ui.selectedFeatures.on("add", function(ev) {
        var feature = ev.element;
        feature.preSelectStyle = feature.getStyle();
        feature.setStyle(null);
    });
    ui.selectedFeatures.on("remove", function(ev) {
        var feature = ev.element;
        feature.setStyle(feature.preSelectStyle);
        delete feature.preSelectStyle;
    });
    // layer/source for modiftying annotation features
    ui.featureOverlay = new ol.layer.Vector({
        source: new ol.source.Vector({ features: ui.features })
    });
    ui.featureOverlay.set("id", "annotations");
    ui.featureOverlay.set("name", "My Annotations");
    // collection for tracking selected features

    // add new points to annotations layer
    ui.pointInter = new ol.interaction.Draw({
        type: "Point",
        features: ui.features
    });

    // add new lines to annotations layer
    ui.lineInter = new ol.interaction.Draw({
        type: "LineString",
        features: ui.features
    });

    // add new polygons to annotations layer
    ui.polyInter = new ol.interaction.Draw({
        type: "Polygon",
        features: ui.features
    });

    // next three interacts are bundled into the Select tool
    // allow modifying features by click+dragging
    ui.modifyInter = new ol.interaction.Modify({
        features: ui.features
    });

    // allow dragbox selection of features
    ui.dragSelectInter = new ol.interaction.DragBox();
    // modify selectedFeatures after dragging a box
    ui.dragSelectInter.on("boxend", function() {
        var extent = ui.dragSelectInter.getGeometry().getExtent();
        ui.featureOverlay.getSource().forEachFeatureIntersectingExtent(extent, function(feature) {
            ui.selectedFeatures.push(feature);
        });
    });
    // clear selectedFeatures before dragging a box
    ui.dragSelectInter.on("boxstart", function() {
        ui.selectedFeatures.clear();
    });
    // allow selecting multiple features by clicking
    ui.selectInter = new ol.interaction.Select({
        layers: [ui.featureOverlay],
        features: ui.selectedFeatures
    });

    self.initAnnotations = function() {
        self.catalogue.push({
            init: function() {
                return ui.featureOverlay;
            },
            id: "annotations",
            name: "My Annotations"
        });
        var defaultTool = {
            name: "Pan",
            icon: "fa-hand-paper-o",
            interactions: [
                self.dragPanInter,
                self.doubleClickZoomInter
            ]
        };
        ui.annotations = new Vue({
            el: "#menu-tab-annotations",
            data: {
                tool: defaultTool,
                tools: [
                    defaultTool,
                    {
                        name: "Select",
                        icon: "fa-mouse-pointer",
                        interactions: [
                            ui.selectInter,
                            ui.dragSelectInter,
                            ui.modifyInter
                        ]
                    },
                    {
                        name: "Point",
                        icon: "/static/images/iD-sprite.svg#icon-point",
                        interactions: [ui.pointInter]
                    },
                    {
                        name: "Line",
                        icon: "/static/images/iD-sprite.svg#icon-line",
                        interactions: [ui.lineInter]
                    },
                    {
                        name: "Polygon",
                        icon: "/static/images/iD-sprite.svg#icon-area",
                        interactions: [ui.polyInter]
                    }
                ],
                features: ui.features,
                featureOverlay: ui.featureOverlay,
                size: 12,
                colour: "#cc0000",
                colours: [
                    ["red", "#cc0000"],
                    ["orange", "#f57900"],
                    ["yellow", "#edd400"],
                    ["green", "#73d216"],
                    ["blue", "#3465a4"],
                    ["violet", "#75507b"],
                    ["brown", "#8f5902"],
                    ["grey", "#555753"],
                    ["black", "#000000"]
                ],
                advanced: false
            },
            methods: {
                icon: function(t) {
                    if (t.icon.startsWith("fa-")) {
                        return '<i class="fa '+t.icon+'" aria-hidden="true"></i>';
                    } else {
                        return '<svg class="icon"><use xlink:href="'+t.icon+'"></use></svg>';
                    }
                },
                setTool: function(t) {
                    var vm = this;
                    vm.tools.forEach(function(tool) {
                        tool.interactions.forEach(function (inter) {
                            self.map.removeInteraction(inter);
                        });
                    });
                    t.interactions.forEach(function (inter) {
                        self.map.addInteraction(inter);
                    });
                    ui.layers.hoverInfo = ((t.name == 'Pan') && (ui.layers.hoverInfoCache));
                    if (!self.getLayer("annotations").olLayer()) {
                        ui.catalogue.onLayerChange(self.getLayer("annotations"), true);
                    }
                    vm.tool = t;
                }
            }
        });
    }

    $self.on("init_map", function() {
        // setup scale events
        self.map.on("postrender", function() {
            if (self.mapScaleControl) {
                self.mapScaleControl.scale = self.getScale();
            }
            if (self.mapExportControls) {
                history.replaceState(null, null, location.pathname + "?" + self.mapExportControls.shortUrl);
            }
        });
        if (document.querySelector("#info")) { self.initInfo() };
        if (document.querySelector("#menu-tab-layers")) { self.initLayers() };
        if (document.querySelector("#menu-tab-annotations")) { self.initAnnotations() };

        $.get("/static/images/legend.svg", function(tmpl) {
            $("#legendsvg").html(tmpl);
            self.initMapControls();
        }, "text");
    });

    return self;
})(window.gokart || {});

<|endoftext|>


<commit_before>// d3.tip
// Copyright (c) 2013 Justin Palmer
// v 0.6.4
//
// Tooltips for d3.js SVG visualizations

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define(['d3'], factory)
  } else {
    // Browser global.
    root.d3.tip = factory(root.d3)
  }
}(this, function (d3) {

  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction = d3_tip_direction,
        offset    = d3_tip_offset,
        html      = d3_tip_html,
        node      = initNode(),
        svg       = null,
        point     = null,
        target    = null

    function tip(vis) {
      svg = getSVGNode(vis)
      point = svg.createSVGPoint()
      document.body.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if(args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = d3.select(node),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop || document.body.scrollTop,
          scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft

      nodel.html(content)
        .style({ opacity: 1, 'pointer-events': 'all' })

      while(i--) nodel.classed(directions[i], false)
      coords = direction_callbacks.get(dir).apply(this)
      nodel.classed(dir, true).style({
        top: (coords.top +  poffset[0]) + scrollTop + 'px',
        left: (coords.left + poffset[1]) + scrollLeft + 'px'
      })

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      nodel = d3.select(node)
      nodel.style({ opacity: 0, 'pointer-events': 'none' })
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.  Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).attr(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.attr.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Proxy style calls to the d3 tip container.  Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).style(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.style.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : d3.functor(v)

      return tip
    }

    function d3_tip_direction() { return 'n' }
    function d3_tip_offset() { return [0, 0] }
    function d3_tip_html() { return ' ' }

    var direction_callbacks = d3.map({
      n:  direction_n,
      s:  direction_s,
      e:  direction_e,
      w:  direction_w,
      nw: direction_nw,
      ne: direction_ne,
      sw: direction_sw,
      se: direction_se
    }),

    directions = direction_callbacks.keys()

    function direction_n() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function direction_s() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function direction_e() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function direction_w() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function direction_nw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function direction_ne() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function direction_sw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function direction_se() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.e.x
      }
    }

    function initNode() {
      var node = d3.select(document.createElement('div'))
      node.style({
        position: 'absolute',
        top: 0,
        opacity: 0,
        'pointer-events': 'none',
        'box-sizing': 'border-box'
      })

      return node.node()
    }

    function getSVGNode(el) {
      el = el.node()
      if(el.tagName.toLowerCase() == 'svg')
        return el

      return el.ownerSVGElement
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
    // sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3.event.target,
          bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w  = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    return tip
  };

}));
<commit_msg>Unset the executable bits.<commit_after>// d3.tip
// Copyright (c) 2013 Justin Palmer
// v 0.6.4
//
// Tooltips for d3.js SVG visualizations

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define(['d3'], factory)
  } else {
    // Browser global.
    root.d3.tip = factory(root.d3)
  }
}(this, function (d3) {

  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction = d3_tip_direction,
        offset    = d3_tip_offset,
        html      = d3_tip_html,
        node      = initNode(),
        svg       = null,
        point     = null,
        target    = null

    function tip(vis) {
      svg = getSVGNode(vis)
      point = svg.createSVGPoint()
      document.body.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if(args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = d3.select(node),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop || document.body.scrollTop,
          scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft

      nodel.html(content)
        .style({ opacity: 1, 'pointer-events': 'all' })

      while(i--) nodel.classed(directions[i], false)
      coords = direction_callbacks.get(dir).apply(this)
      nodel.classed(dir, true).style({
        top: (coords.top +  poffset[0]) + scrollTop + 'px',
        left: (coords.left + poffset[1]) + scrollLeft + 'px'
      })

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      nodel = d3.select(node)
      nodel.style({ opacity: 0, 'pointer-events': 'none' })
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.  Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).attr(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.attr.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Proxy style calls to the d3 tip container.  Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).style(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.style.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : d3.functor(v)

      return tip
    }

    function d3_tip_direction() { return 'n' }
    function d3_tip_offset() { return [0, 0] }
    function d3_tip_html() { return ' ' }

    var direction_callbacks = d3.map({
      n:  direction_n,
      s:  direction_s,
      e:  direction_e,
      w:  direction_w,
      nw: direction_nw,
      ne: direction_ne,
      sw: direction_sw,
      se: direction_se
    }),

    directions = direction_callbacks.keys()

    function direction_n() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function direction_s() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function direction_e() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function direction_w() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function direction_nw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function direction_ne() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function direction_sw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function direction_se() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.e.x
      }
    }

    function initNode() {
      var node = d3.select(document.createElement('div'))
      node.style({
        position: 'absolute',
        top: 0,
        opacity: 0,
        'pointer-events': 'none',
        'box-sizing': 'border-box'
      })

      return node.node()
    }

    function getSVGNode(el) {
      el = el.node()
      if(el.tagName.toLowerCase() == 'svg')
        return el

      return el.ownerSVGElement
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
    // sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3.event.target,
          bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w  = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    return tip
  };

}));
<|endoftext|>


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

define(["jquery"], function ($) {
  // This isn't really a "module" since it just patches jQuery itself

  // walkthrough animations go here

  // animate avatar popping into the dock when they enter the session / telescope in the avatar
  // $.fn.avatarEnter = function (y) {
  //   return this.animate({
  //     scrollTop: y
  //   }, {
  //     duration: 400,
  //     easing: "swing"
  //   });
  // };

  // animate participant cursor and box popping in when they enter the session

  // animate avatar exiting the dock when the exit the session/ telescope out the avatar
  // animate participant cursor and box popping out when they leave the session

  // animate the participant cursor -> rotate down when they're down the page
  // animate the participant cursor -> rotate up when they're on the same frame as the user

  /* Pop in window from dock button: */
  $.fn.popinWindow = function () {

    //start scale small
    //set css width small

    //starting position
    this.css({
      left: "+=74px",
      opacity: 1,
      "zIndex": 8888
    });
    //this.show( { effect: "scale", origin: ['bottom' , 'right'] } );
    //scale larger
    //set css height large
    //this.show( "scale", {percent: 200, direction: 'horizontal' }, 2000 );

    //animate window out
    this.animate({
      opacity: 1,
      left: "-=78px"
    }, {
      duration:60, easing:"linear"
    });
    this.queue();

    //bounce window back
    this.animate({
      left:'+=4px'
    }, {
      duration:60, easing:"linear"
    });
  };

  /* Slide in notification window: */
  $.fn.slideIn = function () {
    this.css({
      //top: "240px",
      left: "+=74px",
      opacity: 0,
      "zIndex": 8888
    });
    return this.animate({
      "left": "-=74px",
      opacity: 1,
      "zIndex": 9999
    }, "fast");
  };

  /* Move notification when another notification slides in */

  /* Used to fade away notification windows + flip the bottom of them out: */
  $.fn.fadeOut = function () {
    this.animate({borderSpacing: -90, opacity: 0.5}, {
      step: function(now, fx) {
        if (fx.prop == "borderSpacing") {
          $(this).css('-webkit-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('-moz-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('-ms-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('-o-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('transform', 'perspective( 600px ) rotateX('+now+'deg)');
        } else {
          $(this).css(fx.prop, now);
        }
      },
      duration: 500
    }, 'linear').promise().then(function () {
      this.css('-webkit-transform', '');
      this.css('-moz-transform', '');
      this.css('-ms-transform', '');
      this.css('-o-transform', '');
      this.css('transform', '');
      this.css("opacity", "");
    });
    return this;
  };

  /* used when user goes down to participant cursor location on screen */
  $.fn.easeTo = function (y) {
    return this.animate({
      scrollTop: y
    }, {
      duration: 400,
      easing: "swing"
    });
  };

  // avatar animate in
  $.fn.animateDockEntry = function () {
    var height = this.height();
    var width = this.width();
    var backgroundSize = height + 4;
    var margin = parseInt(this.css("marginLeft"), 10);

    console.log("calculated", margin, margin + width/2);

    // set starting position CSS for avatar
    this.css({
      marginLeft: margin + width/2,
      height: 0,
      width: 0,
      backgroundSize: "0 0"
    });

    var self = this;

    //then animate avatar to the actual dimensions, and reset the values
    this.animate({
      marginLeft: margin,
      height: height,
      width: width,
      backgroundSize: backgroundSize
    }, {
      duration: 600
    }).promise().then(function () {
      self.css({
        marginLeft: "",
        height: "",
        width: "",
        backgroundSize: ""
      });
    });
    return this;
  };

  // avatar animate out, reverse of above
  $.fn.animateDockExit = function () {
    var height = this.height();
    var width = this.width();
    var backgroundSize = height + 4;
    var margin = parseInt(this.css("marginLeft"), 10);
    console.log("calculated again", margin, margin + width/2);

    // set starting position for the avatar
    this.css({
      marginLeft: margin + width/2,
      height: 40,
      width: 40,
      backgroundSize: "40px 40px"
    });

    //then animate avatar to shrink to nothing, and reset the values again
    // this.animate({
    //   marginLeft: margin,
    //   height: 0,
    //   width: 0,
    //   backgroundSize: "0px 0px"
    // }, {
    //   duration: 600
    // }).promise().then(function () {
    //   self.css({
    //     marginLeft: "",
    //     height: "",
    //     width: "",
    //     backgroundSize: ""
    //   });
    // });

    return this;

  };

  $.fn.animateCursorEntry = function () {
    // Make the cursor bubble pop in
  };

  // keyboard typing animation
  $.fn.animateKeyboard = function () {
    var one = this.find(".towtruck-typing-ellipse-one");
    var two = this.find(".towtruck-typing-ellipse-two");
    var three = this.find(".towtruck-typing-ellipse-three");
    var count = -1;
    var run = (function () {
      count = (count+1) % 4;
      if (count === 0) {
        one.css("opacity", 0.5);
        two.css("opacity", 0.5);
        three.css("opacity", 0.5);
      } else if (count == 1) {
        one.css("opacity", 1);
      } else if (count == 2) {
        two.css("opacity", 1);
      } else { // count==3
        three.css("opacity", 1);
      }
    }).bind(this);
    run();
    var interval = setInterval(run, 300);
    this.data("animateKeyboard", interval);
  };

  $.fn.stopKeyboardAnimation = function () {
    clearTimeout(this.data("animateKeyboard"));
    this.data("animateKeyboard", null);
  };

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

define(["jquery"], function ($) {
  // This isn't really a "module" since it just patches jQuery itself

  // walkthrough animations go here

  // animate avatar popping into the dock when they enter the session / telescope in the avatar
  // $.fn.avatarEnter = function (y) {
  //   return this.animate({
  //     scrollTop: y
  //   }, {
  //     duration: 400,
  //     easing: "swing"
  //   });
  // };

  // animate participant cursor and box popping in when they enter the session

  // animate avatar exiting the dock when the exit the session/ telescope out the avatar
  // animate participant cursor and box popping out when they leave the session

  // animate the participant cursor -> rotate down when they're down the page
  // animate the participant cursor -> rotate up when they're on the same frame as the user

  /* Pop in window from dock button: */
  $.fn.popinWindow = function () {

    //start scale small
    //set css width small

    //starting position
    this.css({
      left: "+=74px",
      opacity: 1,
      "zIndex": 8888
    });
    //this.show( { effect: "scale", origin: ['bottom' , 'right'] } );
    //scale larger
    //set css height large
    //this.show( "scale", {percent: 200, direction: 'horizontal' }, 2000 );

    //animate window out
    this.animate({
      opacity: 1,
      left: "-=78px"
    }, {
      duration:60, easing:"linear"
    });
    this.queue();

    //bounce window back
    this.animate({
      left:'+=4px'
    }, {
      duration:60, easing:"linear"
    });
  };

  /* Slide in notification window: */
  $.fn.slideIn = function () {
    this.css({
      //top: "240px",
      left: "+=74px",
      opacity: 0,
      "zIndex": 8888
    });
    return this.animate({
      "left": "-=74px",
      opacity: 1,
      "zIndex": 9999
    }, "fast");
  };

  /* Move notification when another notification slides in */

  /* Used to fade away notification windows + flip the bottom of them out: */
  $.fn.fadeOut = function () {
    this.animate({borderSpacing: -90, opacity: 0.5}, {
      step: function(now, fx) {
        if (fx.prop == "borderSpacing") {
          $(this).css('-webkit-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('-moz-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('-ms-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('-o-transform', 'perspective( 600px ) rotateX('+now+'deg)')
            .css('transform', 'perspective( 600px ) rotateX('+now+'deg)');
        } else {
          $(this).css(fx.prop, now);
        }
      },
      duration: 500
    }, 'linear').promise().then(function () {
      this.css('-webkit-transform', '');
      this.css('-moz-transform', '');
      this.css('-ms-transform', '');
      this.css('-o-transform', '');
      this.css('transform', '');
      this.css("opacity", "");
    });
    return this;
  };

  /* used when user goes down to participant cursor location on screen */
  $.fn.easeTo = function (y) {
    return this.animate({
      scrollTop: y
    }, {
      duration: 400,
      easing: "swing"
    });
  };

  // avatar animate in
  $.fn.animateDockEntry = function () {
    var height = this.height();
    var width = this.width();
    var backgroundSize = height + 4;
    var margin = parseInt(this.css("marginLeft"), 10);

    console.log("calculated", margin, margin + width/2);

    // set starting position CSS for avatar
    this.css({
      marginLeft: margin + width/2,
      height: 0,
      width: 0,
      backgroundSize: "0 0"
    });

    var self = this;

    //then animate avatar to the actual dimensions, and reset the values
    this.animate({
      marginLeft: margin,
      height: height,
      width: width,
      backgroundSize: backgroundSize
    }, {
      duration: 600
    }).promise().then(function () {
      self.css({
        marginLeft: "",
        height: "",
        width: "",
        backgroundSize: ""
      });
    });
    return this;
  };

  // avatar animate out, reverse of above
  $.fn.animateDockExit = function () {

    // get the current avatar dimenensions
    var height = this.height();
    var width = this.width();
    var backgroundSize = height + 4;
    var margin = parseInt(this.css("marginLeft"), 10);

    //then animate avatar to shrink to nothing, and reset the values again
    this.animate({
      marginLeft: margin + width/2,
      height: 0,
      width: 0,
      backgroundSize: "0 0",
      opacity: 0
    }, 600 );

    return this;

  };

  $.fn.animateCursorEntry = function () {
    // Make the cursor bubble pop in
  };

  // keyboard typing animation
  $.fn.animateKeyboard = function () {
    var one = this.find(".towtruck-typing-ellipse-one");
    var two = this.find(".towtruck-typing-ellipse-two");
    var three = this.find(".towtruck-typing-ellipse-three");
    var count = -1;
    var run = (function () {
      count = (count+1) % 4;
      if (count === 0) {
        one.css("opacity", 0.5);
        two.css("opacity", 0.5);
        three.css("opacity", 0.5);
      } else if (count == 1) {
        one.css("opacity", 1);
      } else if (count == 2) {
        two.css("opacity", 1);
      } else { // count==3
        three.css("opacity", 1);
      }
    }).bind(this);
    run();
    var interval = setInterval(run, 300);
    this.data("animateKeyboard", interval);
  };

  $.fn.stopKeyboardAnimation = function () {
    clearTimeout(this.data("animateKeyboard"));
    this.data("animateKeyboard", null);
  };

});
<|endoftext|>


<commit_before>const should = require('chai').should();

const router = require('./goo.router');

const jsdom = require('jsdom');

const newWindow = (initialPath, callback) => {
    jsdom.env({
        url: 'https://example.com' + initialPath,
        done: (err, window) => {
            const app = router(window);
            callback(window, app);
        },
    });
};

describe('goo-router', () => {
    it('should return a function', () => {
        router.should.be.a('function');
    });

    it('should have a use function', () => {
        const test = router({location: '', document: {origin: ''}});
        test.use.should.be.a('function');
    });

    it('should have a redirect function', () => {
        const test = router({location: '', document: {origin: ''}});
        test.redirect.should.be.a('function');
    });
});

describe('router -> use', () => {
    it('should return an array', () => {
        router({location: '', document: {origin: ''}}).use({}).should.be.an('array');
    });
});

describe('use -> route', () => {
    it('should reject malformed paths', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({
                    route: {
                        path: {},
                        callback: () => {},
                    },
                });
            }).should.throw(Error, /path/);
            done();
        });
    });

    it('should reject malformed callback', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({
                    route: {
                        path: '',
                        callback: '',
                    },
                });
            }).should.throw(Error, /callback/);
            done();
        });
    });

    it('should check the current pathname against new routes', (done) => {
        let test = false;
        newWindow('/test', (window, router) => {
            router.use({
                route: {
                    path: '/test',
                    callback: () => {
                        test = true;
                    },
                },
            });
            test.should.equal(true);
            done();
        });
    });

    it('should save routes for future redirects', (done) => {
        let test = false;
        newWindow('/', (window, router) => {
            router.use({
                route: {
                    path: '/test',
                    callback: () => {
                        test = true;
                    },
                },
            });
            router.redirect('/test');
            test.should.equal(true);
            done();
        });
    });
});

describe('use -> fallback', () => {
    it('should reject malformed fallbacks', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({fallback: true});
            }).should.throw(Error, /fallback/);
            done();
        });
    });

    it('should call the fallback when the route is not found', (done) => {
        let test = false;
        newWindow('/', (window, router) => {
            router.use({
                fallback: () => {
                    test = true;
                },
            });
            router.redirect('/test');
            test.should.equal(true);
            done();
        });
    });

    it('should call the fallback with the path as argument', (done) => {
        let test = null;
        newWindow('/', (window, router) => {
            router.use({
                fallback: (path) => {
                    test = path;
                },
            });
            router.redirect('/test');
            test.should.equal('/test');
            done();
        });
    });
});

describe('use -> base', () => {
    it('should reject malformed inputs', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({base: true});
            }).should.throw(Error, /base/);
            done();
        });
    });

    it('should add the base url to all new pathnames', (done) => {
        newWindow('/', (window, router) => {
            router.use({route: {path: '/test', callback: () => {}}});
            router.use({base: '/testBase'});
            router.redirect('/test');
            window.location.pathname.should.equal('/testBase/test');
            done();
        });
    });

    it('should be applied to the current pathname', (done) => {
        newWindow('/testBase', (window, router) => {
            let test = false;
            router.use({route: {
                path: '',
                callback: () => {
                    test = true;
                },
            }});
            test.should.equal(false);
            router.use({base: '/testBase'});
            test.should.equal(true);
            done();
        });
    });
});

describe('redirect', () => {
    it('should reject malformed paths', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.redirect(undefined);
            }).should.throw(Error, /path/);
            done();
        });
    });

    it('should accept empty params', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({route: {
                    path: '/',
                    callback: () => {},
                }});
                router.redirect('/');
            }).should.not.throw(Error, /params/);
            done();
        });
    });

    it('should change the pathname', (done) => {
        newWindow('/', (window, router) => {
            router.use({route: {path: '/test/xyz', callback: () => {}}});
            router.redirect('/test/xyz');
            window.location.pathname.should.equal('/test/xyz');
            done();
        });
    });

    it('should accumulate params and pass them to the callback', (done) => {
        let test = null;
        newWindow('/', (window, router) => {
            router.use({
                route: {
                    path: '/user/:id/fetch/:field',
                    callback: (params) => {
                        test = params;
                    },
                },
            });
            router.redirect('/user/123/fetch/name');
            test.id.should.equal('123');
            test.field.should.equal('name');
            done();
        });
    });
});
<commit_msg>closes #41<commit_after>const should = require('chai').should();

const router = require('./goo.router');

const jsdom = require('jsdom');

const newWindow = (initialPath, callback) => {
    jsdom.env({
        url: 'https://example.com' + initialPath,
        done: (err, window) => {
            const app = router(window);
            callback(window, app);
        },
    });
};

describe('goo-router', () => {
    it('should return a function', () => {
        router.should.be.a('function');
    });

    it('should have a use function', () => {
        const test = router({location: '', document: {origin: ''}});
        test.use.should.be.a('function');
    });

    it('should have a redirect function', () => {
        const test = router({location: '', document: {origin: ''}});
        test.redirect.should.be.a('function');
    });
});

describe('router -> use', () => {
    it('should return an array', () => {
        router({location: '', document: {origin: ''}}).use({}).should.be.an('array');
    });
});

describe('use -> route', () => {
    it('should reject malformed paths', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({
                    route: {
                        path: {},
                        callback: () => {},
                    },
                });
            }).should.throw(Error, /path/);
            done();
        });
    });

    it('should reject malformed callback', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({
                    route: {
                        path: '',
                        callback: '',
                    },
                });
            }).should.throw(Error, /callback/);
            done();
        });
    });

    it('should check the current pathname against new routes', (done) => {
        let test = false;
        newWindow('/test', (window, router) => {
            router.use({
                route: {
                    path: '/test',
                    callback: () => {
                        test = true;
                    },
                },
            });
            test.should.equal(true);
            done();
        });
    });

    it('should save routes for future redirects', (done) => {
        let test = false;
        newWindow('/', (window, router) => {
            router.use({
                route: {
                    path: '/test',
                    callback: () => {
                        test = true;
                    },
                },
            });
            router.redirect('/test');
            test.should.equal(true);
            done();
        });
    });

    it('should prioritize the earliest routes', (done) => {
        let test = false;
        newWindow('/', (window, router) => {
            router.use({
                route: [
                    {
                        path: '/*',
                        callback: () => {
                            test = 1;
                        },
                    },
                    {
                        path: '/test',
                        callback: () => {
                            test = 2;
                        },
                    },
                ],
            });
            router.redirect('/test');
            test.should.equal(1);
            done();
        });
    });
});

describe('use -> fallback', () => {
    it('should reject malformed fallbacks', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({fallback: true});
            }).should.throw(Error, /fallback/);
            done();
        });
    });

    it('should call the fallback when the route is not found', (done) => {
        let test = false;
        newWindow('/', (window, router) => {
            router.use({
                fallback: () => {
                    test = true;
                },
            });
            router.redirect('/test');
            test.should.equal(true);
            done();
        });
    });

    it('should call the fallback with the path as argument', (done) => {
        let test = null;
        newWindow('/', (window, router) => {
            router.use({
                fallback: (path) => {
                    test = path;
                },
            });
            router.redirect('/test');
            test.should.equal('/test');
            done();
        });
    });
});

describe('use -> base', () => {
    it('should reject malformed inputs', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({base: true});
            }).should.throw(Error, /base/);
            done();
        });
    });

    it('should add the base url to all new pathnames', (done) => {
        newWindow('/', (window, router) => {
            router.use({route: {path: '/test', callback: () => {}}});
            router.use({base: '/testBase'});
            router.redirect('/test');
            window.location.pathname.should.equal('/testBase/test');
            done();
        });
    });

    it('should be applied to the current pathname', (done) => {
        newWindow('/testBase', (window, router) => {
            let test = false;
            router.use({route: {
                path: '',
                callback: () => {
                    test = true;
                },
            }});
            test.should.equal(false);
            router.use({base: '/testBase'});
            test.should.equal(true);
            done();
        });
    });
});

describe('redirect', () => {
    it('should reject malformed paths', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.redirect(undefined);
            }).should.throw(Error, /path/);
            done();
        });
    });

    it('should accept empty params', (done) => {
        newWindow('/', (window, router) => {
            (() => {
                router.use({route: {
                    path: '/',
                    callback: () => {},
                }});
                router.redirect('/');
            }).should.not.throw(Error, /params/);
            done();
        });
    });

    it('should change the pathname', (done) => {
        newWindow('/', (window, router) => {
            router.use({route: {path: '/test/xyz', callback: () => {}}});
            router.redirect('/test/xyz');
            window.location.pathname.should.equal('/test/xyz');
            done();
        });
    });

    it('should accumulate params and pass them to the callback', (done) => {
        let test = null;
        newWindow('/', (window, router) => {
            router.use({
                route: {
                    path: '/user/:id/fetch/:field',
                    callback: (params) => {
                        test = params;
                    },
                },
            });
            router.redirect('/user/123/fetch/name');
            test.id.should.equal('123');
            test.field.should.equal('name');
            done();
        });
    });
});
<|endoftext|>


<commit_before>import { MessageContext } from '../intl/context';
import { Localization } from '../lib/localization';

import { getDirection } from './shims';
import {
  initMutationObserver, translateRoots, observe, disconnect
} from './observer';
import {
  setAttributes, getAttributes, translateFragment
} from './dom';

const properties = new WeakMap();
const contexts = new WeakMap();

export class HTMLLocalization extends Localization {
  constructor(doc, requestBundles) {
    super();
    this.interactive = requestBundles();
    this.ready = this.interactive
      .then(bundles => fetchFirstBundle(bundles))
      .then(bundles => translateDocument(this, bundles));

    properties.set(this, { doc, requestBundles, ready: false });
    initMutationObserver(this);
    this.observeRoot(doc.documentElement);
  }

  requestLanguages(requestedLangs) {
    return this.ready = this.interactive.then(
      bundles => changeLanguages(this, bundles, requestedLangs)
    );
  }

  handleEvent() {
    return this.requestLanguages();
  }

  formatEntities(...keys) {
    // XXX add async fallback
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatEntityFromContext
      )
    );
  }

  formatValues(...keys) {
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatValueFromContext
      )
    );
  }

  formatValue(id, args) {
    return this.formatValues([id, args]).then(
      ([val]) => val
    );
  }

  translateFragment(frag) {
    return translateFragment(this, frag);
  }

  observeRoot(root) {
    observe(this, root);
  }

  disconnectRoot(root) {
    disconnect(this, root);
  }
}

HTMLLocalization.prototype.setAttributes = setAttributes;
HTMLLocalization.prototype.getAttributes = getAttributes;

function createContextFromBundle(bundle) {
  return bundle.fetch().then(resources => {
    const ctx = new MessageContext(bundle.lang);
    resources.forEach(res => ctx.addMessages(res));
    contexts.set(bundle, ctx);
    return ctx;
  });
}

function fetchFirstBundle(bundles) {
  const [bundle] = bundles;
  return createContextFromBundle(bundle).then(
    () => bundles
  );
}

function changeLanguages(l10n, oldBundles, requestedLangs) {
  const { requestBundles } = properties.get(l10n);

  l10n.interactive = requestBundles(requestedLangs).then(
    newBundles => equal(oldBundles, newBundles) ?
      oldBundles : fetchFirstBundle(newBundles)
  );

  return l10n.interactive.then(
    bundles => translateDocument(l10n, bundles)
  );
}

function equal(bundles1, bundles2) {
  return bundles1.length === bundles2.length &&
    bundles1.every(({lang}, i) => lang === bundles2[i].lang);
}

function translateDocument(l10n, bundles) {
  const langs = bundles.map(bundle => bundle.lang);
  const props = properties.get(l10n);
  const html = props.doc.documentElement;

  if (props.ready) {
    return translateRoots(l10n).then(
      () => setAllAndEmit(html, langs)
    );
  }

  const translated = translateRoots(l10n).then(
    () => setLangDir(html, langs)
  );

  return translated.then(() => {
    setLangs(html, langs);
    props.ready = true;
  });
}

function setLangs(html, langs) {
  html.setAttribute('langs', langs.join(' '));
}

function setLangDir(html, [lang]) {
  html.setAttribute('lang', lang);
  html.setAttribute('dir', getDirection(lang));
}

function setAllAndEmit(html, langs) {
  setLangDir(html, langs);
  setLangs(html, langs);
  html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated', {
    bubbles: false,
    cancelable: false,
  }));
}
<commit_msg>Refactor translateDocument<commit_after>import { MessageContext } from '../intl/context';
import { Localization } from '../lib/localization';

import { getDirection } from './shims';
import {
  initMutationObserver, translateRoots, observe, disconnect
} from './observer';
import {
  setAttributes, getAttributes, translateFragment
} from './dom';

const properties = new WeakMap();
const contexts = new WeakMap();

export class HTMLLocalization extends Localization {
  constructor(doc, requestBundles) {
    super();
    this.interactive = requestBundles();
    this.ready = this.interactive
      .then(bundles => fetchFirstBundle(bundles))
      .then(bundles => translateDocument(this, bundles));

    properties.set(this, { doc, requestBundles, ready: false });
    initMutationObserver(this);
    this.observeRoot(doc.documentElement);
  }

  requestLanguages(requestedLangs) {
    return this.ready = this.interactive.then(
      bundles => changeLanguages(this, bundles, requestedLangs)
    );
  }

  handleEvent() {
    return this.requestLanguages();
  }

  formatEntities(...keys) {
    // XXX add async fallback
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatEntityFromContext
      )
    );
  }

  formatValues(...keys) {
    return this.interactive.then(
      ([bundle]) => this._formatKeysFromContext(
        contexts.get(bundle), keys, this._formatValueFromContext
      )
    );
  }

  formatValue(id, args) {
    return this.formatValues([id, args]).then(
      ([val]) => val
    );
  }

  translateFragment(frag) {
    return translateFragment(this, frag);
  }

  observeRoot(root) {
    observe(this, root);
  }

  disconnectRoot(root) {
    disconnect(this, root);
  }
}

HTMLLocalization.prototype.setAttributes = setAttributes;
HTMLLocalization.prototype.getAttributes = getAttributes;

function createContextFromBundle(bundle) {
  return bundle.fetch().then(resources => {
    const ctx = new MessageContext(bundle.lang);
    resources.forEach(res => ctx.addMessages(res));
    contexts.set(bundle, ctx);
    return ctx;
  });
}

function fetchFirstBundle(bundles) {
  const [bundle] = bundles;
  return createContextFromBundle(bundle).then(
    () => bundles
  );
}

function changeLanguages(l10n, oldBundles, requestedLangs) {
  const { requestBundles } = properties.get(l10n);

  l10n.interactive = requestBundles(requestedLangs).then(
    newBundles => equal(oldBundles, newBundles) ?
      oldBundles : fetchFirstBundle(newBundles)
  );

  return l10n.interactive.then(
    bundles => translateDocument(l10n, bundles)
  );
}

function equal(bundles1, bundles2) {
  return bundles1.length === bundles2.length &&
    bundles1.every(({lang}, i) => lang === bundles2[i].lang);
}

function translateDocument(l10n, bundles) {
  const langs = bundles.map(bundle => bundle.lang);
  const props = properties.get(l10n);
  const html = props.doc.documentElement;

  function setLangs() {
    html.setAttribute('langs', langs.join(' '));
    html.setAttribute('lang', langs[0]);
    html.setAttribute('dir', getDirection(langs[0]));
  }

  function emit() {
    html.parentNode.dispatchEvent(new CustomEvent('DOMRetranslated', {
      bubbles: false,
      cancelable: false,
    }));
  }

  const next = props.ready ?
    emit : () => props.ready = true;

  return translateRoots(l10n)
    .then(setLangs)
    .then(next);
}
<|endoftext|>


<commit_before>var $ = require('jquery');
var Block = require('../block');

var getTemplate = function(params) {
    var template = '';

    template += '
';
    template += '';
    template += '
';

    return template;
};

module.exports = Block.extend({
    title: 'iFrame',
    type: 'iframe',
    icon_name: 'iframe',

    pastable: true,
    paste_options: {
        html: '<input type="text" placeholder="' + i18n.t('blocks:iframe:placeholder') + '" class="st-block__paste-input st-paste-block">'
    },

    controllable: true,
    controls_position: 'top',
    controls_visible: false,
    controls: [
        {
            slug: 'height',
            eventTrigger: 'change',
            fn: function(e) {
                e.preventDefault();

                this.$iframe.css('height', e.target.value);

                this.setData({
                    height: e.target.value
                });
            },
            html: '<input type="number" placeholder="Hauteur" />'
        },
        {
            slug: 'scroll-toggle',
            eventTrigger: 'change',
            fn: function(e) {
                e.preventDefault();

                this.$iframe.attr('scrolling', e.target.value);

                // We need to set the src again to trigger the iframe refresh (to see the scrolling change) (required on Chrome 40 at least)
                var src = this.$iframe.attr('src');
                this.$iframe.attr('src', src);

                this.setData({
                    scrolling: e.target.value
                });
            },
            html: '<select><option value="no">Sans scroll</option><option value="yes">Avec scroll</option></select>'
        }
    ],

    onBlockRender: function() {
        this.$iframeWrapper.appendTo(this.$inner);
    },

    beforeBlockRender: function() {
        var template = getTemplate({
            height: 300,
            scrolling: 'no',
            visible: false
        });

        this.$iframeWrapper = $(template);
        this.$iframe = this.$iframeWrapper.find('iframe');
    },

    loadData: function(data) {
        this.$iframe.attr('src', data.src);
        this.$iframe.show();
    },

    onContentPasted: function(event) {
        this.setAndLoadData({
            src: $(event.target).val()
        });

        this.showControls();
    }
});
<commit_msg>this.$editor should be used in blocks<commit_after>var $ = require('jquery');
var _ = require('../lodash.js');
var Block = require('../block');

var getTemplate = function(params) {
    var template = '';

    template += '
';
    template += '';
    template += '
';

    return template;
};

module.exports = Block.extend({
    title: 'iFrame',
    type: 'iframe',
    icon_name: 'iframe',

    pastable: true,
    paste_options: {
        html: '<input type="text" placeholder="' + i18n.t('blocks:iframe:placeholder') + '" class="st-block__paste-input st-paste-block">'
    },

    controllable: true,
    controls_position: 'top',
    controls_visible: false,
    controls: [
        {
            slug: 'height',
            eventTrigger: 'change',
            fn: function(e) {
                e.preventDefault();

                this.$iframe.css('height', e.target.value);

                this.setData({
                    height: e.target.value
                });
            },
            html: '<input type="number" placeholder="Hauteur" />'
        },
        {
            slug: 'scroll-toggle',
            eventTrigger: 'change',
            fn: function(e) {
                e.preventDefault();

                this.$iframe.attr('scrolling', e.target.value);

                // We need to set the src again to trigger the iframe refresh (to see the scrolling change) (required on Chrome 40 at least)
                var src = this.$iframe.attr('src');
                this.$iframe.attr('src', src);

                this.setData({
                    scrolling: e.target.value
                });
            },
            html: '<select><option value="no">Sans scroll</option><option value="yes">Avec scroll</option></select>'
        }
    ],

    editorHTML: '

',

    onBlockRender: function() {
        this.$editor.append(this.$iframeWrapper);
    },

    beforeBlockRender: function() {
        var template = getTemplate({
            height: 300,
            scrolling: 'no',
            visible: false
        });

        this.$iframeWrapper = $(template);
        this.$iframe = this.$iframeWrapper.find('iframe');
    },

    loadData: function(data) {
        this.$iframe.attr('src', data.src);
        this.$iframe.show();
    },

    onContentPasted: function(event) {
        this.setAndLoadData({
            src: $(event.target).val()
        });

        this.showControls();
    }
});
<|endoftext|>