<commit_before>var request = require.safe('request'),
    giphyCache = {};

exports.match = function(text, commandPrefix) {
    return text.startsWith(commandPrefix + 'gif');
};

exports.help = function(commandPrefix) {
    return [[commandPrefix + 'gif <query>','Searches for animated GIF.']];
};

exports.search = function (query, callback, waitCallback) {
    var cacheq = query.trim().toLowerCase();
    var index = 0;
    if (giphyCache[cacheq]) {
        if (new Date().getTime() - giphyCache[cacheq].dt > 86400000
            || giphyCache[cacheq].index + 1 == giphyCache[cacheq].length) {
            delete giphyCache[cacheq];
        }
        else {
            giphyCache[cacheq].index++;
            index = giphyCache[cacheq].index;
        }
    }

    waitCallback();

    var q = {
        q: query,
        limit: 10,
        api_key: exports.config.apiKey,
        rating: 'pg-13'
    };

    request.get({url: 'http://api.giphy.com/v1/gifs/search', qs: q}, function(error, response, body) {
        body = JSON.parse(body);
        if (response.statusCode === 200 && body && body.data) {
            var images = body.data;
            if (images && images.length > 0) {
                var image = images[index].url;
                if (!giphyCache[cacheq]) {
                    giphyCache[cacheq] = {
                        dt: new Date(),
                        index: index,
                        length: images.length
                    };
                }
                callback(image);
            }
            else {
                callback({error:'No images found.'});
            }
        }
        else {
            callback({error:'Whomever the system admin is around here, I demand that they should be fired.'});
        }
    });
};

// Taken from sassy. Copyright sassy authors
exports.ensureExt = function (url) {
    if (!/(.gif|.jpe?g|.png|.gifv)$/i.test(url)) {
        url += '#.png';
    }
    return url;
};

exports.run = function(api, event) {
    if (!exports.config.apiKey) {
        api.sendMessage("My admin needs to give me a Giphy API key before gif will work.\nPlease set the value apiKey in config.json to a Giphy API key.", event.thread_id);
        return;
    }

    var query = event.body.substr(6);
    exports.search(query, function(image) {
            if (image) {
                var url = exports.ensureExt(image);
                api.sendImage("url", url, "I found this:", event.thread_id);
            }
            else {
                api.sendMessage("Something went very wrong.", event.thread_id);
            }
        },
        function() {
            api.sendTyping(event.thread_id);
        });
};
<commit_msg>Fixed wrong query extraction for Giphy module<commit_after>var request = require.safe('request'),
    giphyCache = {};

exports.match = function(text, commandPrefix) {
    return text.startsWith(commandPrefix + 'gif');
};

exports.help = function(commandPrefix) {
    return [[commandPrefix + 'gif <query>','Searches for animated GIF.']];
};

exports.search = function (query, callback, waitCallback) {
    var cacheq = query.trim().toLowerCase();
    var index = 0;
    if (giphyCache[cacheq]) {
        if (new Date().getTime() - giphyCache[cacheq].dt > 86400000
            || giphyCache[cacheq].index + 1 == giphyCache[cacheq].length) {
            delete giphyCache[cacheq];
        }
        else {
            giphyCache[cacheq].index++;
            index = giphyCache[cacheq].index;
        }
    }

    waitCallback();

    var q = {
        q: query,
        limit: 10,
        api_key: exports.config.apiKey,
        rating: 'pg-13'
    };

    request.get({url: 'http://api.giphy.com/v1/gifs/search', qs: q}, function(error, response, body) {
        body = JSON.parse(body);
        if (response.statusCode === 200 && body && body.data) {
            var images = body.data;
            if (images && images.length > 0) {
                var image = images[index].url;
                if (!giphyCache[cacheq]) {
                    giphyCache[cacheq] = {
                        dt: new Date(),
                        index: index,
                        length: images.length
                    };
                }
                callback(image);
            }
            else {
                callback({error:'No images found.'});
            }
        }
        else {
            callback({error:'Whomever the system admin is around here, I demand that they should be fired.'});
        }
    });
};

// Taken from sassy. Copyright sassy authors
exports.ensureExt = function (url) {
    if (!/(.gif|.jpe?g|.png|.gifv)$/i.test(url)) {
        url += '#.png';
    }
    return url;
};

exports.run = function(api, event) {
    if (!exports.config.apiKey) {
        api.sendMessage("My admin needs to give me a Giphy API key before gif will work.\nPlease set the value apiKey in config.json to a Giphy API key.", event.thread_id);
        return;
    }

    var query = event.body.substr(5);

    if(!query || query.length == 0) {
        api.sendMessage("Of course, I'll look for an empty string!", event.thread_id);
        return;
    }

    exports.search(query, function(image) {
            if (image) {
                var url = exports.ensureExt(image);
                api.sendImage("url", url, "I found this:", event.thread_id);
            }
            else {
                api.sendMessage("Something went very wrong.", event.thread_id);
            }
        },
        function() {
            api.sendTyping(event.thread_id);
        });
};
<|endoftext|>


<commit_before>define([
    'jquery',
    'knockout',
    'underscore',
    'viewmodels/widget',
    'arches',
    'mapbox-gl',
    'mapbox-gl-draw',
    'knockout-mapping',
    'geojson-extent',
    'select2v4',
    'bindings/select2v4',
    'bindings/fadeVisible',
    'bindings/mapbox-gl',
    'bindings/chosen'
], function($, ko, _, WidgetViewModel, arches, mapboxgl, Draw, koMapping, geojsonExtent) {
    /**
     * knockout components namespace used in arches
     * @external "ko.components"
     * @see http://knockoutjs.com/documentation/component-binding.html
     */

    /**
     * registers a geometry-widget component for use in forms
     * @function external:"ko.components".geometry-widget
     * @param {object} params
     * @param {boolean} params.value - the value being managed
     * @param {object} params.config -
     * @param {string} params.config.zoom - map zoom level
     * @param {string} params.config.centerX - map center longitude
     * @param {string} params.config.centerY - map center latitude
     * @param {string} params.config.geocoder - the text string id of the geocoder api (currently MapzenGeocoder or BingGeocoder).
     * @param {string} params.config.basemap - the layer name of the selected basemap to be shown in the map
     * @param {string} params.config.geometryTypes - the geometry types available for a user to edit
     * @param {string} params.config.pitch - the pitch of the map in degrees
     * @param {string} params.config.bearing - the bearing of the map in degrees with north at 0
     */
    return ko.components.register('geometry-widget', {
        viewModel: function(params) {
            var self = this;
            this.reportHeader = params.type === 'report-header' ? true : false;
            this.resourceEditor = params.type === 'resource-editor' ? true : false;
            this.configType = params.reportHeader || 'header';
            params.configKeys = ['zoom', 'centerX', 'centerY', 'geocoder', 'basemap', 'geometryTypes', 'pitch', 'bearing', 'geocodePlaceholder', 'geocoderVisible', 'minZoom', 'maxZoom'];
            WidgetViewModel.apply(this, [params]);

            if (params.graph !== undefined) {
                this.resourceIcon = params.graph.get('iconclass');
                this.resourceName = params.graph.get('name');
                this.resourceColor = params.graph.get('mapfeaturecolor');
                this.resourcePointSize = params.graph.get('mappointsize');
                this.resourceLineWidth = params.graph.get('maplinewidth');
            }

            this.selectedBasemap = this.basemap;

            this.mapToolsExpanded = ko.observable(false);
            this.geocodeShimAdded = ko.observable(false);
            this.mapToolsExpanded.subscribe(function(expanded) {
                self.geocodeShimAdded(expanded);
            });
            this.layers = _.clone(arches.mapLayers);

            this.geocoderOptions = ko.observableArray([{
                'id': 'BingGeocoder',
                'name': 'Bing'
            }, {
                'id': 'MapzenGeocoder',
                'name': 'Mapzen'
            }]);

            this.geocodeUrl = arches.urls.geocoder;
            this.geocodeResponseOption = ko.observable();
            this.selectedItems = ko.observableArray()
            this.mapControlPanels = {
                basemaps: ko.observable(false),
                overlays: ko.observable(true),
                maptools: ko.observable(true),
                legend: ko.observable(true)
            };

            this.defineResourceLayer = function() {
                var resourceLayer = {
                    name: this.resourceName,
                    layer_definitions: [{
                        "id": "resource-poly",
                        "source": "resource",
                        "type": "fill",
                        "layout": {},
                        "filter": ["!in", "$type", "LineString"],
                        "paint": {
                            "fill-color": this.resourceColor,
                            "fill-opacity": 0.8
                        }
                    }, {
                        "id": "resource-point",
                        "source": "resource",
                        "type": "circle",
                        "layout": {},
                        "filter": ["!in", "$type", "LineString", "Polygon"],
                        "paint": {
                            "circle-radius": 5,
                            "circle-color": this.resourceColor,
                            "circle-opacity": 0.8
                        }
                    }, {
                        "id": "resource-line",
                        "source": "resource",
                        "type": "line",
                        "layout": {},
                        "paint": {
                            "line-color": this.resourceColor,
                            "line-opacity": 0.8,
                            "line-width": 2.5
                        }
                    }],
                    isoverlay: true,
                    sortorder: 4,
                    icon: this.resourceIcon
                };
                return resourceLayer;
            }

            this.addInitialLayers = function() {
                this.layers.push(this.defineResourceLayer());
                var initialLayers = [];
                var overlayLayers = _.sortBy(_.where(this.layers, {
                    isoverlay: true
                }), 'sortorder').reverse();

                this.layers.forEach(function(mapLayer) {
                    if (mapLayer.name === this.basemap()) {
                        _.each(mapLayer.layer_definitions, function(layer) {
                            initialLayers.push(layer);
                        });
                    }
                }, this);

                overlayLayers.forEach(function(overlayLayer) {
                    _.each(overlayLayer.layer_definitions, function(layer) {
                        initialLayers.push(layer);
                    });
                })

                initialLayers.push({
                    "id": "geocode-point",
                    "source": "geocode-point",
                    "type": "circle",
                    "paint": {
                        "circle-radius": 5,
                        "circle-color": "red"
                    }
                });

                return initialLayers;
            }

            this.editingToolIcons = {
                Point: 'ion-location',
                Line: 'ion-steam',
                Polygon: 'fa fa-pencil-square-o',
                Delete: 'ion-trash-a'
            }

            this.toggleGeocoder = function(self, evt) {
                if (self.geocoderVisible() === true) {
                    self.geocoderVisible(false)
                } else {
                    self.geocoderVisible(true)
                }
            }

            this.setupMap = function(map) {

                var self = this;
                var draw = Draw();
                this.map = map;
                this.draw = draw;
                this.map.addControl(draw);
                this.redrawGeocodeLayer = function() {
                    var cacheLayer = map.getLayer('geocode-point');
                    map.removeLayer('geocode-point');
                    map.addLayer(cacheLayer, 'gl-draw-active-line.hot');
                }

                this.map.on('load', function() {
                    if (!self.configForm) {
                        var source = self.map.getSource('resource')
                        var features = [];
                        var result = {
                            "type": "FeatureCollection",
                            "features": []
                        };
                        var data = null;
                        if (self.reportHeader === true && !ko.isObservable(self.value)) {
                            self.value.forEach(function(tile) {
                                _.each(tile.data, function(val, key) {
                                    if (_.contains(val, 'FeatureCollection')) {
                                        result.features = _.union(result.features, val.features)
                                    }
                                }, self);
                            }, self)
                            data = result;
                        } else if (self.reportHeader === false && !ko.isObservable(self.value)) {
                            data = koMapping.toJS(self.value);
                        } else { //if values are for a form widget...
                            if (_.isObject(self.value())) { //confirm value is not "", null, or undefined
                                data = koMapping.toJS(self.value);
                            }
                        };
                        if (data) {
                            source.setData(data)
                            var bounds = new mapboxgl.LngLatBounds(geojsonExtent(data));
                            var tr = this.transform;
                            var nw = tr.project(bounds.getNorthWest());
                            var se = tr.project(bounds.getSouthEast());
                            var size = se.sub(nw);
                            var scaleX = (tr.width - 80) / size.x;
                            var scaleY = (tr.height - 80) / size.y;

                            var options = {
                                center: tr.unproject(nw.add(se).div(2)),
                                zoom: Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), Infinity)
                            };
                            self.map.jumpTo(options);
                        }
                    }
                });

                this.selectedItems.subscribe(function(e) {
                    var coords = e.geometry.coordinates;
                    this.map.getSource('geocode-point').setData(e.geometry);
                    this.redrawGeocodeLayer();
                    var centerPoint = new mapboxgl.LngLat(coords[0], coords[1])
                    this.map.flyTo({
                        center: centerPoint
                    });
                }, this);

                this.selectEditingTool = function(val, e) {
                    switch (val) {
                        case 'Point':
                            draw.changeMode('draw_point');
                            break;
                        case 'Line':
                            draw.changeMode('draw_line_string');
                            break;
                        case 'Polygon':
                            draw.changeMode('draw_polygon');
                            break;
                        default:
                            draw.trash();
                    }
                }

                this.dataReturn =
                    function(term, page) {
                        return {
                            q: term,
                            geocoder: self.geocoder()
                        };
                    }

                this.selectSetup = {
                    ajax: {
                        url: arches.urls.geocoder,
                        dataType: 'json',
                        quietMillis: 250,
                        data: this.dataReturn,
                        results: function(data, page) {
                            return {
                                results: data.results
                            };
                        },
                        cache: true
                    },
                    minimumInputLength: 4,
                    multiple: false,
                    maximumSelectionSize: 1,
                    placeholder: this.geocodePlaceholder()
                };

                this.createOverlays = function() {
                    var overlays =
                        _.each(_.where(this.layers, {
                            isoverlay: true
                        }), function(overlay) {
                            _.extend(overlay, {
                                opacity: ko.observable(100),
                                color: _.filter(overlay.layer_definitions[0].paint, function(prop, key) {
                                    if (key.includes('-color')) {
                                        return prop
                                    };
                                })[0],
                                showingTools: ko.observable(false),
                                invisible: ko.observable(false),
                                toggleOverlayTools: function(e) {
                                    this.showingTools(!this.showingTools());
                                },
                                toggleOverlayVisibility: function(e) {
                                    this.opacity() > 0.0 ? this.opacity(0.0) : this.opacity(100.0);
                                },
                                updateOpacity: function(val) {
                                    val > 0.0 ? this.invisible(false) : this.invisible(true);
                                    this.layer_definitions.forEach(function(layer) {
                                        this.setPaintProperty(layer.id, layer.type + '-opacity', Number(val) / 100.0);
                                    }, map)
                                }
                            });
                            overlay.opacity.subscribe(function(value) {
                                overlay.updateOpacity(value);
                            });
                        });

                    return overlays;
                }

                this.overlays = ko.observableArray(this.createOverlays())

                this.basemaps = _.filter(arches.mapLayers, function(baselayer) {
                    return baselayer.isoverlay === false
                });

                this.setBasemap = function(basemapType) {
                    var lowestOverlay = _.last(_.last(this.overlays()).layer_definitions);
                    this.basemaps.forEach(function(basemap) {
                        var self = this;
                        if (basemap.name === basemapType.name) {
                            basemap.layer_definitions.forEach(function(layer) {
                                self.map.addLayer(layer, lowestOverlay.id)
                            })
                        } else {
                            basemap.layer_definitions.forEach(function(layer) {
                                if (self.map.getLayer(layer.id) !== undefined) {
                                    self.map.removeLayer(layer.id);
                                }
                            })
                        }
                    }, this)
                };

                this.updateConfigs = function() {
                    var self = this;
                    return function() {
                        var mapCenter = this.getCenter()
                        var zoom = self.map.getZoom()
                        if (self.zoom() !== zoom) {
                            self.zoom(zoom);
                        };
                        self.centerX(mapCenter.lng);
                        self.centerY(mapCenter.lat);
                        self.bearing(this.getBearing());
                        self.pitch(this.getPitch());
                    }
                }

                this.saveGeometries = function() {
                    var self = this;
                    return function() {
                        var currentDrawing = self.draw.getAll()
                        if (self.value.features !== undefined) {
                            currentDrawing.features.forEach(function(feature) {
                                self.value.features.push(feature)
                            })
                        } else {
                            self.value(currentDrawing)
                        }
                    }
                }

                this.map.on('moveend', this.updateConfigs());
                this.map.on('draw.create', this.saveGeometries())
                this.map.on('draw.delete', this.saveGeometries())

                this.overlays.subscribe(function(overlays) {
                    var anchorLayer = 'gl-draw-active-line.hot';
                    for (var i = overlays.length; i-- > 0;) { //Using a conventional loop because we want to go backwards over the array without creating a copy
                        overlays[i].layer_definitions.forEach(function(layer) {
                            map.removeLayer(layer.id)
                        })
                    }
                    for (var i = overlays.length; i-- > 0;) {
                        overlays[i].layer_definitions.forEach(function(layer) {
                            map.addLayer(layer, anchorLayer);
                            map.setPaintProperty(layer.id, layer.type + '-opacity', overlays[i].opacity() / 100.0);
                        })
                    }
                    this.redrawGeocodeLayer();
                }, this)
            }

            this.onGeocodeSelection = function(val, e) {
                this.geocoder(e.currentTarget.value)
            }

            this.toggleMapTools = function(data, event) {
                data.mapToolsExpanded(!data.mapToolsExpanded());
            }

            this.toggleMapControlPanels = function(data, event) {
                var panel = data;
                _.each(self.mapControlPanels, function(panelValue, panelName) {
                    panelName === panel ? panelValue(false) : panelValue(true);
                });
            }

            this.moveOverlay = function(overlay, direction) {
                var overlays = ko.utils.unwrapObservable(self.overlays);
                var source = ko.utils.arrayIndexOf(overlays, overlay);
                var target = (direction === 'up') ? source - 1 : source + 1;

                if (target >= 0 && target < overlays.length) {
                    self.overlays.valueWillMutate();

                    overlays.splice(source, 1);
                    overlays.splice(target, 0, overlay);

                    self.overlays.valueHasMutated();
                }
            };

            this.sources = _.clone(arches.mapSources);
            this.sources["resource"] = {
                "type": "geojson",
                "data": {
                    "type": "FeatureCollection",
                    "features": []
                }
            };

            this.mapStyle = {
                "version": 8,
                "name": "Basic",
                "metadata": {
                    "mapbox:autocomposite": true,
                    "mapbox:type": "template"
                },
                "sources": this.sources,
                "sprite": "mapbox://sprites/mapbox/basic-v9",
                "glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
                "layers": []
            };

            this.mapStyle.layers = this.addInitialLayers();

            this.selectBasemap = function(val) {
                self.basemap(val.name)
                self.setBasemap(val);
            }
        },
        template: {
            require: 'text!widget-templates/geometry'
        }
    });
});
<commit_msg>fixed overlay layer ordering bug in which overlays were getting masked by basemaps, re #723<commit_after>define([
    'jquery',
    'knockout',
    'underscore',
    'viewmodels/widget',
    'arches',
    'mapbox-gl',
    'mapbox-gl-draw',
    'knockout-mapping',
    'geojson-extent',
    'select2v4',
    'bindings/select2v4',
    'bindings/fadeVisible',
    'bindings/mapbox-gl',
    'bindings/chosen'
], function($, ko, _, WidgetViewModel, arches, mapboxgl, Draw, koMapping, geojsonExtent) {
    /**
     * knockout components namespace used in arches
     * @external "ko.components"
     * @see http://knockoutjs.com/documentation/component-binding.html
     */

    /**
     * registers a geometry-widget component for use in forms
     * @function external:"ko.components".geometry-widget
     * @param {object} params
     * @param {boolean} params.value - the value being managed
     * @param {object} params.config -
     * @param {string} params.config.zoom - map zoom level
     * @param {string} params.config.centerX - map center longitude
     * @param {string} params.config.centerY - map center latitude
     * @param {string} params.config.geocoder - the text string id of the geocoder api (currently MapzenGeocoder or BingGeocoder).
     * @param {string} params.config.basemap - the layer name of the selected basemap to be shown in the map
     * @param {string} params.config.geometryTypes - the geometry types available for a user to edit
     * @param {string} params.config.pitch - the pitch of the map in degrees
     * @param {string} params.config.bearing - the bearing of the map in degrees with north at 0
     */
    return ko.components.register('geometry-widget', {
        viewModel: function(params) {
            var self = this;
            this.reportHeader = params.type === 'report-header' ? true : false;
            this.resourceEditor = params.type === 'resource-editor' ? true : false;
            this.configType = params.reportHeader || 'header';
            params.configKeys = ['zoom', 'centerX', 'centerY', 'geocoder', 'basemap', 'geometryTypes', 'pitch', 'bearing', 'geocodePlaceholder', 'geocoderVisible', 'minZoom', 'maxZoom'];
            WidgetViewModel.apply(this, [params]);

            if (params.graph !== undefined) {
                this.resourceIcon = params.graph.get('iconclass');
                this.resourceName = params.graph.get('name');
                this.resourceColor = params.graph.get('mapfeaturecolor');
                this.resourcePointSize = params.graph.get('mappointsize');
                this.resourceLineWidth = params.graph.get('maplinewidth');
            }

            this.selectedBasemap = this.basemap;

            this.mapToolsExpanded = ko.observable(false);
            this.geocodeShimAdded = ko.observable(false);
            this.mapToolsExpanded.subscribe(function(expanded) {
                self.geocodeShimAdded(expanded);
            });
            this.layers = _.clone(arches.mapLayers);

            this.geocoderOptions = ko.observableArray([{
                'id': 'BingGeocoder',
                'name': 'Bing'
            }, {
                'id': 'MapzenGeocoder',
                'name': 'Mapzen'
            }]);

            this.geocodeUrl = arches.urls.geocoder;
            this.geocodeResponseOption = ko.observable();
            this.selectedItems = ko.observableArray()
            this.mapControlPanels = {
                basemaps: ko.observable(false),
                overlays: ko.observable(true),
                maptools: ko.observable(true),
                legend: ko.observable(true)
            };

            this.defineResourceLayer = function() {
                var resourceLayer = {
                    name: this.resourceName,
                    layer_definitions: [{
                        "id": "resource-poly",
                        "source": "resource",
                        "type": "fill",
                        "layout": {},
                        "filter": ["!in", "$type", "LineString"],
                        "paint": {
                            "fill-color": this.resourceColor,
                            "fill-opacity": 0.8
                        }
                    }, {
                        "id": "resource-point",
                        "source": "resource",
                        "type": "circle",
                        "layout": {},
                        "filter": ["!in", "$type", "LineString", "Polygon"],
                        "paint": {
                            "circle-radius": 5,
                            "circle-color": this.resourceColor,
                            "circle-opacity": 0.8
                        }
                    }, {
                        "id": "resource-line",
                        "source": "resource",
                        "type": "line",
                        "layout": {},
                        "paint": {
                            "line-color": this.resourceColor,
                            "line-opacity": 0.8,
                            "line-width": 2.5
                        }
                    }],
                    isoverlay: true,
                    sortorder: 4,
                    icon: this.resourceIcon
                };
                return resourceLayer;
            }

            this.addInitialLayers = function() {
                this.layers.push(this.defineResourceLayer());
                var initialLayers = [];
                var overlayLayers = _.sortBy(_.where(this.layers, {
                    isoverlay: true
                }), 'sortorder').reverse();

                this.layers.forEach(function(mapLayer) {
                    if (mapLayer.name === this.basemap()) {
                        _.each(mapLayer.layer_definitions, function(layer) {
                            initialLayers.push(layer);
                        });
                    }
                }, this);

                overlayLayers.forEach(function(overlayLayer) {
                    _.each(overlayLayer.layer_definitions, function(layer) {
                        initialLayers.push(layer);
                    });
                })

                initialLayers.push({
                    "id": "geocode-point",
                    "source": "geocode-point",
                    "type": "circle",
                    "paint": {
                        "circle-radius": 5,
                        "circle-color": "red"
                    }
                });

                return initialLayers;
            }

            this.editingToolIcons = {
                Point: 'ion-location',
                Line: 'ion-steam',
                Polygon: 'fa fa-pencil-square-o',
                Delete: 'ion-trash-a'
            }

            this.toggleGeocoder = function(self, evt) {
                if (self.geocoderVisible() === true) {
                    self.geocoderVisible(false)
                } else {
                    self.geocoderVisible(true)
                }
            }

            this.setupMap = function(map) {

                var self = this;
                var draw = Draw();
                this.map = map;
                this.draw = draw;
                this.map.addControl(draw);
                this.redrawGeocodeLayer = function() {
                    var cacheLayer = map.getLayer('geocode-point');
                    map.removeLayer('geocode-point');
                    map.addLayer(cacheLayer, 'gl-draw-active-line.hot');
                }

                this.map.on('load', function() {
                    if (!self.configForm) {
                        var source = self.map.getSource('resource')
                        var features = [];
                        var result = {
                            "type": "FeatureCollection",
                            "features": []
                        };
                        var data = null;
                        if (self.reportHeader === true && !ko.isObservable(self.value)) {
                            self.value.forEach(function(tile) {
                                _.each(tile.data, function(val, key) {
                                    if (_.contains(val, 'FeatureCollection')) {
                                        result.features = _.union(result.features, val.features)
                                    }
                                }, self);
                            }, self)
                            data = result;
                        } else if (self.reportHeader === false && !ko.isObservable(self.value)) {
                            data = koMapping.toJS(self.value);
                        } else { //if values are for a form widget...
                            if (_.isObject(self.value())) { //confirm value is not "", null, or undefined
                                data = koMapping.toJS(self.value);
                            }
                        };
                        if (data) {
                            source.setData(data)
                            var bounds = new mapboxgl.LngLatBounds(geojsonExtent(data));
                            var tr = this.transform;
                            var nw = tr.project(bounds.getNorthWest());
                            var se = tr.project(bounds.getSouthEast());
                            var size = se.sub(nw);
                            var scaleX = (tr.width - 80) / size.x;
                            var scaleY = (tr.height - 80) / size.y;

                            var options = {
                                center: tr.unproject(nw.add(se).div(2)),
                                zoom: Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), Infinity)
                            };
                            self.map.jumpTo(options);
                        }
                    }
                });

                this.selectedItems.subscribe(function(e) {
                    var coords = e.geometry.coordinates;
                    this.map.getSource('geocode-point').setData(e.geometry);
                    this.redrawGeocodeLayer();
                    var centerPoint = new mapboxgl.LngLat(coords[0], coords[1])
                    this.map.flyTo({
                        center: centerPoint
                    });
                }, this);

                this.selectEditingTool = function(val, e) {
                    switch (val) {
                        case 'Point':
                            draw.changeMode('draw_point');
                            break;
                        case 'Line':
                            draw.changeMode('draw_line_string');
                            break;
                        case 'Polygon':
                            draw.changeMode('draw_polygon');
                            break;
                        default:
                            draw.trash();
                    }
                }

                this.dataReturn =
                    function(term, page) {
                        return {
                            q: term,
                            geocoder: self.geocoder()
                        };
                    }

                this.selectSetup = {
                    ajax: {
                        url: arches.urls.geocoder,
                        dataType: 'json',
                        quietMillis: 250,
                        data: this.dataReturn,
                        results: function(data, page) {
                            return {
                                results: data.results
                            };
                        },
                        cache: true
                    },
                    minimumInputLength: 4,
                    multiple: false,
                    maximumSelectionSize: 1,
                    placeholder: this.geocodePlaceholder()
                };

                this.createOverlays = function() {
                    var overlays =
                        _.each(_.where(this.layers, {
                            isoverlay: true
                        }), function(overlay) {
                            _.extend(overlay, {
                                opacity: ko.observable(100),
                                color: _.filter(overlay.layer_definitions[0].paint, function(prop, key) {
                                    if (key.includes('-color')) {
                                        return prop
                                    };
                                })[0],
                                showingTools: ko.observable(false),
                                invisible: ko.observable(false),
                                toggleOverlayTools: function(e) {
                                    this.showingTools(!this.showingTools());
                                },
                                toggleOverlayVisibility: function(e) {
                                    this.opacity() > 0.0 ? this.opacity(0.0) : this.opacity(100.0);
                                },
                                updateOpacity: function(val) {
                                    val > 0.0 ? this.invisible(false) : this.invisible(true);
                                    this.layer_definitions.forEach(function(layer) {
                                        this.setPaintProperty(layer.id, layer.type + '-opacity', Number(val) / 100.0);
                                    }, map)
                                }
                            });
                            overlay.opacity.subscribe(function(value) {
                                overlay.updateOpacity(value);
                            });
                        });

                    return overlays;
                }

                this.overlays = ko.observableArray(this.createOverlays())

                this.basemaps = _.filter(arches.mapLayers, function(baselayer) {
                    return baselayer.isoverlay === false
                });

                this.setBasemap = function(basemapType) {
                    var lowestOverlay = _.first(_.last(this.overlays()).layer_definitions);
                    this.basemaps.forEach(function(basemap) {
                        var self = this;
                        if (basemap.name === basemapType.name) {
                            basemap.layer_definitions.forEach(function(layer) {
                                self.map.addLayer(layer, lowestOverlay.id)
                            })
                        } else {
                            basemap.layer_definitions.forEach(function(layer) {
                                if (self.map.getLayer(layer.id) !== undefined) {
                                    self.map.removeLayer(layer.id);
                                }
                            })
                        }
                    }, this)
                };

                this.updateConfigs = function() {
                    var self = this;
                    return function() {
                        var mapCenter = this.getCenter()
                        var zoom = self.map.getZoom()
                        if (self.zoom() !== zoom) {
                            self.zoom(zoom);
                        };
                        self.centerX(mapCenter.lng);
                        self.centerY(mapCenter.lat);
                        self.bearing(this.getBearing());
                        self.pitch(this.getPitch());
                    }
                }

                this.saveGeometries = function() {
                    var self = this;
                    return function() {
                        var currentDrawing = self.draw.getAll()
                        if (self.value.features !== undefined) {
                            currentDrawing.features.forEach(function(feature) {
                                self.value.features.push(feature)
                            })
                        } else {
                            self.value(currentDrawing)
                        }
                    }
                }

                this.map.on('moveend', this.updateConfigs());
                this.map.on('draw.create', this.saveGeometries())
                this.map.on('draw.delete', this.saveGeometries())

                this.overlays.subscribe(function(overlays) {
                    var anchorLayer = 'gl-draw-active-line.hot';
                    for (var i = overlays.length; i-- > 0;) { //Using a conventional loop because we want to go backwards over the array without creating a copy
                        overlays[i].layer_definitions.forEach(function(layer) {
                            map.removeLayer(layer.id)
                        })
                    }
                    for (var i = overlays.length; i-- > 0;) {
                        overlays[i].layer_definitions.forEach(function(layer) {
                            map.addLayer(layer, anchorLayer);
                            map.setPaintProperty(layer.id, layer.type + '-opacity', overlays[i].opacity() / 100.0);
                        })
                    }
                    this.redrawGeocodeLayer();
                }, this)
            }

            this.onGeocodeSelection = function(val, e) {
                this.geocoder(e.currentTarget.value)
            }

            this.toggleMapTools = function(data, event) {
                data.mapToolsExpanded(!data.mapToolsExpanded());
            }

            this.toggleMapControlPanels = function(data, event) {
                var panel = data;
                _.each(self.mapControlPanels, function(panelValue, panelName) {
                    panelName === panel ? panelValue(false) : panelValue(true);
                });
            }

            this.moveOverlay = function(overlay, direction) {
                var overlays = ko.utils.unwrapObservable(self.overlays);
                var source = ko.utils.arrayIndexOf(overlays, overlay);
                var target = (direction === 'up') ? source - 1 : source + 1;

                if (target >= 0 && target < overlays.length) {
                    self.overlays.valueWillMutate();

                    overlays.splice(source, 1);
                    overlays.splice(target, 0, overlay);

                    self.overlays.valueHasMutated();
                }
            };

            this.sources = _.clone(arches.mapSources);
            this.sources["resource"] = {
                "type": "geojson",
                "data": {
                    "type": "FeatureCollection",
                    "features": []
                }
            };

            this.mapStyle = {
                "version": 8,
                "name": "Basic",
                "metadata": {
                    "mapbox:autocomposite": true,
                    "mapbox:type": "template"
                },
                "sources": this.sources,
                "sprite": "mapbox://sprites/mapbox/basic-v9",
                "glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
                "layers": []
            };

            this.mapStyle.layers = this.addInitialLayers();

            this.selectBasemap = function(val) {
                self.basemap(val.name)
                self.setBasemap(val);
            }
        },
        template: {
            require: 'text!widget-templates/geometry'
        }
    });
});
<|endoftext|>


<commit_before>/*
 * The algorithms here are based on Brandes and Köpf, "Fast and Simple
 * Horizontal Coordinate Assignment".
 */
dagre.layout.position = (function() {
  function actualNodeWidth(u) {
    var uAttrs = u.attrs;
    return uAttrs.width + uAttrs.marginX * 2 + uAttrs.strokeWidth;
  }

  function actualNodeHeight(u) {
    var uAttrs = u.attrs;
    return uAttrs.height + uAttrs.marginY * 2 + uAttrs.strokeWidth;
  }

  function markType1Conflicts(layering) {
    var pos = {};
    layering[0].forEach(function(u, i) {
      pos[u.id()] = i;
    });

    for (var i = 1; i < layering.length; ++i) {
      var layer = layering[i];

      // Position of last inner segment in the previous layer
      var innerLeft = 0;
      var currIdx = 0;

      // Scan current layer for next node with an inner segment.
      for (var j = 0; j < layer.length; ++j) {
        var u = layer[j];
        // Update positions map for next layer iteration
        pos[u.id()] = j;

        // Search for the next inner segment in the previous layer
        var innerRight = null;
        u.predecessors().forEach(function(v) {
          // TODO could abort as soon as we find a dummy
          if (u.attrs.dummy || v.attrs.dummy) {
            innerRight = pos[v.id()];
          }
        });

        // If no inner segment but at the end of the list we still
        // need to check for type 1 conflicts with earlier segments
        if (innerRight === null && j === layer.length - 1) {
          innerRight = layer.length;
        }

        if (innerRight !== null) {
          for (;currIdx <= j; ++currIdx) {
            var v = layer[currIdx];
            v.inEdges().forEach(function(e) {
              var tailPos = pos[e.tail().id()];
              if (tailPos < innerLeft || tailPos > innerRight) {
                e.attrs.type1Conflict = true;
              }
            });
          }
          innerLeft = innerRight;
        }
      }
    }
  }

  function verticalAlignment(layering, relationship) {
    var pos = {};
    var root = {};
    var align = {};

    layering.forEach(function(layer) {
      layer.forEach(function(u, i) {
        root[u.id()] = u;
        align[u.id()] = u;
        pos[u.id()] = i;
      });
    });

    layering.forEach(function(layer) {
      var prevIdx = -1;
      layer.forEach(function(v) {
        var related = v[relationship]();
        if (related.length > 0) {
          // TODO could find medians with linear algorithm if performance warrants it.
          related.sort(function(x, y) { return pos[x] - pos[y]; });
          var mid = (related.length - 1) / 2;
          related.slice(Math.floor(mid), Math.ceil(mid) + 1).forEach(function(u) {
            if (align[v.id()].id() === v.id()) {
              // TODO should we collapse multi-edges for vertical alignment?

              // Only need to check first returned edge for a type 1 conflict
              if (!u.edges(v)[0].attrs.type1Conflict && prevIdx < pos[u.id()]) {
                align[u.id()] = v;
                align[v.id()] = root[v.id()] = root[u.id()];
                prevIdx = pos[u.id()];
              }
            }
          });
        }
      });
    });

    return { pos: pos, root: root, align: align };
  }

  /*
   * Determines how much spacing u needs from its origin (center) to satisfy
   * width, margin, stroke, and node separation.
   */
  function deltaX(u, nodeSep, edgeSep) {
    var sep = u.attrs.dummy ? edgeSep : nodeSep;
    return actualNodeWidth(u) / 2 + sep / 2;
  }

  function horizontalCompaction(layering, pos, root, align, nodeSep, edgeSep) {
    // Mapping of node id -> sink node id for class
    var sink = {};

    // Mapping of sink node id -> x delta
    var shift = {};

    // Mapping of node id -> predecessor node id (or null)
    var pred = {};

    // Calculated X positions
    var xs = {};

    layering.forEach(function(layer) {
      layer.forEach(function(u, i) {
        sink[u.id()] = u;
        shift[u.id()] = Number.POSITIVE_INFINITY;
        pred[u.id()] = i > 0 ? layer[i - 1].id() : null;
      });
    });

    function placeBlock(v) {
      var vId = v.id();
      if (xs[vId] === undefined) {
        xs[vId] = 0;
        var wId = vId;
        do {
          if (pos[wId] > 0) {
            var u = root[pred[wId]];
            placeBlock(u);
            if (sink[vId].id() === vId) {
              sink[vId] = sink[u.id()];
            }
            var delta = deltaX(u, nodeSep, edgeSep) + deltaX(v, nodeSep, edgeSep);
            if (sink[vId] !== sink[u.id()]) {
              shift[sink[u.id()]] = Math.min(shift[sink[u.id()]], v.attrs.x - u.attrs.x - delta);
            } else {
              xs[vId] = Math.max(xs[vId], xs[u.id()] + delta);
            }
          }
          wId = align[wId].id();
        } while (wId !== vId);
      }
    }

    // Root coordinates relative to sink
    Object.keys(root).forEach(function(uId) {
      if (root[uId].id() === uId) {
        placeBlock(root[uId]);
      }
    });

    // Absolute coordinates
    concat(layering).forEach(function(u) {
      xs[u.id()] = xs[root[u.id()].id()];
      var xDelta = shift[sink[root[u.id()].id()]];
      if (xDelta < Number.POSITIVE_INFINITY) {
        xs[u.id()] += xDelta;
      }
    });

    return xs;
  }

  function findMinCoord(layering, xs) {
    return min(layering.map(function(layer) {
      var u = layer[0];
      return xs[u.id()] - actualNodeWidth(u) / 2;
    }));
  }

  function findMaxCoord(layering, xs) {
    return max(layering.map(function(layer) {
      var u = layer[layer.length - 1];
      return xs[u.id()] - actualNodeWidth(u) / 2;
    }));
  }

  function shiftX(delta, xs) {
    Object.keys(xs).forEach(function(x) {
      xs[x] += delta;
    });
  }

  function alignToSmallest(layering, xss) {
    // First find the smallest width
    var smallestWidthMinCoord;
    var smallestWidthMaxCoord;
    var smallestWidth = Number.POSITIVE_INFINITY;
    values(xss).forEach(function(xs) {
      var minCoord = findMinCoord(layering, xs);
      var maxCoord = findMaxCoord(layering, xs);
      var width = maxCoord - minCoord;
      if (width < smallestWidth) {
        smallestWidthMinCoord = minCoord;
        smallestWidthMaxCoord = maxCoord;
        smallestWidth = width;
      }
    });

    // Realign coordinates with smallest width
    ["up", "down"].forEach(function(vertDir) {
      var xs = xss[vertDir + "-left"];
      var delta = smallestWidthMinCoord - findMinCoord(layering, xs);
      if (delta) {
        shiftX(delta, xs);
      }
    });

    ["up", "down"].forEach(function(vertDir) {
      var xs = xss[vertDir + "-right"];
      var delta = smallestWidthMaxCoord - findMaxCoord(layering, xs);
      if (delta) {
        shiftX(delta, xs);
      }
    });
  }

  function flipHorizontally(layering, xs) {
    var maxCenter = max(values(xs));
    Object.keys(xs).forEach(function(uId) {
      xs[uId] = maxCenter - xs[uId];
    });
  }

  function reverseInnerOrder(layering) {
    layering.forEach(function(layer) {
      layer.reverse();
    });
  }

  return function(g, layering) {
    markType1Conflicts(layering);

    var xss = {};
    ["up", "down"].forEach(function(vertDir) {
      if (vertDir === "down") { layering.reverse(); }

      ["left", "right"].forEach(function(horizDir) {
        if (horizDir === "right") { reverseInnerOrder(layering); }

        var dir = vertDir + "-" + horizDir;
        if (!("debugPosDir" in g.attrs) || g.attrs.debugPosDir === dir) {
          var align = verticalAlignment(layering, vertDir === "up" ? "predecessors" : "successors");
          xss[dir]= horizontalCompaction(layering, align.pos, align.root, align.align, g.attrs.nodeSep, g.attrs.edgeSep);
          if (horizDir === "right") { flipHorizontally(layering, xss[dir]); }
        }

        if (horizDir === "right") { reverseInnerOrder(layering); }
      });

      if (vertDir === "down") { layering.reverse(); }
    });

    if (g.attrs.debugPosDir) {
      // In debug mode we allow forcing layout to a particular alignment.
      g.nodes().forEach(function(u) {
        u.attrs.x = xss[g.attrs.debugPosDir][u.id()];
      });
    } else {
      alignToSmallest(layering, xss);

      // Find average of medians for xss array
      g.nodes().forEach(function(u) {
        var xs = values(xss).map(function(xs) { return xs[u.id()]; }).sort();
        u.attrs.x = (xs[1] + xs[2]) / 2;
      });
    }

    // Align min center point with 0
    var minX = min(g.nodes().map(function(u) { return u.attrs.x - actualNodeWidth(u) / 2; }));
    g.nodes().forEach(function(u) {
      u.attrs.x -= minX;
    });

    // Align y coordinates with ranks
    var posY = 0;
    for (var i = 0; i < layering.length; ++i) {
      var layer = layering[i];
      var height = max(layer.map(actualNodeHeight));
      posY += height / 2;
      for (var j = 0; j < layer.length; ++j) {
        var uAttrs = layer[j].attrs;
        uAttrs.y = posY;
      }
      posY += height / 2 + g.attrs.rankSep;
    }
  };
})();
<commit_msg>Fix sorting for adjacent nodes in position phase<commit_after>/*
 * The algorithms here are based on Brandes and Köpf, "Fast and Simple
 * Horizontal Coordinate Assignment".
 */
dagre.layout.position = (function() {
  function actualNodeWidth(u) {
    var uAttrs = u.attrs;
    return uAttrs.width + uAttrs.marginX * 2 + uAttrs.strokeWidth;
  }

  function actualNodeHeight(u) {
    var uAttrs = u.attrs;
    return uAttrs.height + uAttrs.marginY * 2 + uAttrs.strokeWidth;
  }

  function markType1Conflicts(layering) {
    var pos = {};
    layering[0].forEach(function(u, i) {
      pos[u.id()] = i;
    });

    for (var i = 1; i < layering.length; ++i) {
      var layer = layering[i];

      // Position of last inner segment in the previous layer
      var innerLeft = 0;
      var currIdx = 0;

      // Scan current layer for next node with an inner segment.
      for (var j = 0; j < layer.length; ++j) {
        var u = layer[j];
        // Update positions map for next layer iteration
        pos[u.id()] = j;

        // Search for the next inner segment in the previous layer
        var innerRight = null;
        u.predecessors().forEach(function(v) {
          // TODO could abort as soon as we find a dummy
          if (u.attrs.dummy || v.attrs.dummy) {
            innerRight = pos[v.id()];
          }
        });

        // If no inner segment but at the end of the list we still
        // need to check for type 1 conflicts with earlier segments
        if (innerRight === null && j === layer.length - 1) {
          innerRight = layer.length;
        }

        if (innerRight !== null) {
          for (;currIdx <= j; ++currIdx) {
            var v = layer[currIdx];
            v.inEdges().forEach(function(e) {
              var tailPos = pos[e.tail().id()];
              if (tailPos < innerLeft || tailPos > innerRight) {
                e.attrs.type1Conflict = true;
              }
            });
          }
          innerLeft = innerRight;
        }
      }
    }
  }

  function verticalAlignment(layering, relationship) {
    var pos = {};
    var root = {};
    var align = {};

    layering.forEach(function(layer) {
      layer.forEach(function(u, i) {
        root[u.id()] = u;
        align[u.id()] = u;
        pos[u.id()] = i;
      });
    });

    layering.forEach(function(layer) {
      var prevIdx = -1;
      layer.forEach(function(v) {
        var related = v[relationship]();
        if (related.length > 0) {
          // TODO could find medians with linear algorithm if performance warrants it.
          related.sort(function(x, y) { return pos[x.id()] - pos[y.id()]; });
          var mid = (related.length - 1) / 2;
          related.slice(Math.floor(mid), Math.ceil(mid) + 1).forEach(function(u) {
            if (align[v.id()].id() === v.id()) {
              // TODO should we collapse multi-edges for vertical alignment?

              // Only need to check first returned edge for a type 1 conflict
              if (!u.edges(v)[0].attrs.type1Conflict && prevIdx < pos[u.id()]) {
                align[u.id()] = v;
                align[v.id()] = root[v.id()] = root[u.id()];
                prevIdx = pos[u.id()];
              }
            }
          });
        }
      });
    });

    return { pos: pos, root: root, align: align };
  }

  /*
   * Determines how much spacing u needs from its origin (center) to satisfy
   * width, margin, stroke, and node separation.
   */
  function deltaX(u, nodeSep, edgeSep) {
    var sep = u.attrs.dummy ? edgeSep : nodeSep;
    return actualNodeWidth(u) / 2 + sep / 2;
  }

  function horizontalCompaction(layering, pos, root, align, nodeSep, edgeSep) {
    // Mapping of node id -> sink node id for class
    var sink = {};

    // Mapping of sink node id -> x delta
    var shift = {};

    // Mapping of node id -> predecessor node id (or null)
    var pred = {};

    // Calculated X positions
    var xs = {};

    layering.forEach(function(layer) {
      layer.forEach(function(u, i) {
        sink[u.id()] = u;
        shift[u.id()] = Number.POSITIVE_INFINITY;
        pred[u.id()] = i > 0 ? layer[i - 1].id() : null;
      });
    });

    function placeBlock(v) {
      var vId = v.id();
      if (xs[vId] === undefined) {
        xs[vId] = 0;
        var wId = vId;
        do {
          if (pos[wId] > 0) {
            var u = root[pred[wId]];
            placeBlock(u);
            if (sink[vId].id() === vId) {
              sink[vId] = sink[u.id()];
            }
            var delta = deltaX(u, nodeSep, edgeSep) + deltaX(v, nodeSep, edgeSep);
            if (sink[vId] !== sink[u.id()]) {
              shift[sink[u.id()]] = Math.min(shift[sink[u.id()]], v.attrs.x - u.attrs.x - delta);
            } else {
              xs[vId] = Math.max(xs[vId], xs[u.id()] + delta);
            }
          }
          wId = align[wId].id();
        } while (wId !== vId);
      }
    }

    // Root coordinates relative to sink
    Object.keys(root).forEach(function(uId) {
      if (root[uId].id() === uId) {
        placeBlock(root[uId]);
      }
    });

    // Absolute coordinates
    concat(layering).forEach(function(u) {
      xs[u.id()] = xs[root[u.id()].id()];
      var xDelta = shift[sink[root[u.id()].id()]];
      if (xDelta < Number.POSITIVE_INFINITY) {
        xs[u.id()] += xDelta;
      }
    });

    return xs;
  }

  function findMinCoord(layering, xs) {
    return min(layering.map(function(layer) {
      var u = layer[0];
      return xs[u.id()] - actualNodeWidth(u) / 2;
    }));
  }

  function findMaxCoord(layering, xs) {
    return max(layering.map(function(layer) {
      var u = layer[layer.length - 1];
      return xs[u.id()] - actualNodeWidth(u) / 2;
    }));
  }

  function shiftX(delta, xs) {
    Object.keys(xs).forEach(function(x) {
      xs[x] += delta;
    });
  }

  function alignToSmallest(layering, xss) {
    // First find the smallest width
    var smallestWidthMinCoord;
    var smallestWidthMaxCoord;
    var smallestWidth = Number.POSITIVE_INFINITY;
    values(xss).forEach(function(xs) {
      var minCoord = findMinCoord(layering, xs);
      var maxCoord = findMaxCoord(layering, xs);
      var width = maxCoord - minCoord;
      if (width < smallestWidth) {
        smallestWidthMinCoord = minCoord;
        smallestWidthMaxCoord = maxCoord;
        smallestWidth = width;
      }
    });

    // Realign coordinates with smallest width
    ["up", "down"].forEach(function(vertDir) {
      var xs = xss[vertDir + "-left"];
      var delta = smallestWidthMinCoord - findMinCoord(layering, xs);
      if (delta) {
        shiftX(delta, xs);
      }
    });

    ["up", "down"].forEach(function(vertDir) {
      var xs = xss[vertDir + "-right"];
      var delta = smallestWidthMaxCoord - findMaxCoord(layering, xs);
      if (delta) {
        shiftX(delta, xs);
      }
    });
  }

  function flipHorizontally(layering, xs) {
    var maxCenter = max(values(xs));
    Object.keys(xs).forEach(function(uId) {
      xs[uId] = maxCenter - xs[uId];
    });
  }

  function reverseInnerOrder(layering) {
    layering.forEach(function(layer) {
      layer.reverse();
    });
  }

  return function(g, layering) {
    markType1Conflicts(layering);

    var xss = {};
    ["up", "down"].forEach(function(vertDir) {
      if (vertDir === "down") { layering.reverse(); }

      ["left", "right"].forEach(function(horizDir) {
        if (horizDir === "right") { reverseInnerOrder(layering); }

        var dir = vertDir + "-" + horizDir;
        if (!("debugPosDir" in g.attrs) || g.attrs.debugPosDir === dir) {
          var align = verticalAlignment(layering, vertDir === "up" ? "predecessors" : "successors");
          xss[dir]= horizontalCompaction(layering, align.pos, align.root, align.align, g.attrs.nodeSep, g.attrs.edgeSep);
          if (horizDir === "right") { flipHorizontally(layering, xss[dir]); }
        }

        if (horizDir === "right") { reverseInnerOrder(layering); }
      });

      if (vertDir === "down") { layering.reverse(); }
    });

    if (g.attrs.debugPosDir) {
      // In debug mode we allow forcing layout to a particular alignment.
      g.nodes().forEach(function(u) {
        u.attrs.x = xss[g.attrs.debugPosDir][u.id()];
      });
    } else {
      alignToSmallest(layering, xss);

      // Find average of medians for xss array
      g.nodes().forEach(function(u) {
        var xs = values(xss).map(function(xs) { return xs[u.id()]; }).sort();
        u.attrs.x = (xs[1] + xs[2]) / 2;
      });
    }

    // Align min center point with 0
    var minX = min(g.nodes().map(function(u) { return u.attrs.x - actualNodeWidth(u) / 2; }));
    g.nodes().forEach(function(u) {
      u.attrs.x -= minX;
    });

    // Align y coordinates with ranks
    var posY = 0;
    for (var i = 0; i < layering.length; ++i) {
      var layer = layering[i];
      var height = max(layer.map(actualNodeHeight));
      posY += height / 2;
      for (var j = 0; j < layer.length; ++j) {
        var uAttrs = layer[j].attrs;
        uAttrs.y = posY;
      }
      posY += height / 2 + g.attrs.rankSep;
    }
  };
})();
<|endoftext|>


<commit_before>import Relay from 'react-relay/classic';

class PipelineScheduleCreate extends Relay.Mutation {
  static fragments = {
    pipeline: () => Relay.QL`
      fragment on Pipeline {
        id
      }
    `
  }

  getMutation() {
    return Relay.QL`
      mutation {
        pipelineScheduleCreate
      }
    `;
  }

  getFatQuery() {
    return Relay.QL`
      fragment on PipelineScheduleCreatePayload {
        pipelineScheduleEdge,
        pipeline {
          schedules {
            count
          }
        }
      }
    `;
  }

  getConfigs() {
    return [{
      type: 'REQUIRED_CHILDREN',
      children: [
        Relay.QL`
          fragment on PipelineScheduleCreatePayload {
            pipelineScheduleEdge {
              node {
                uuid
                pipeline {
                  schedules {
                    count
                  }
                }
              }
            }
          }
        `
      ]
    }, {
      type: 'RANGE_ADD',
      parentName: 'pipeline',
      parentID: this.props.pipeline.id,
      connectionName: 'schedules',
      edgeName: 'pipelineScheduleEdge',
      rangeBehaviors: {
        '': 'append'
      }
    }];
  }

  getVariables() {
    return {
      pipelineID: this.props.pipeline.id,
      cronline: this.props.cronline,
      label: this.props.label,
      message: this.props.message,
      commit: this.props.commit,
      branch: this.props.branch,
      env: this.props.env
    };
  }
}

export default PipelineScheduleCreate;
<commit_msg>Add "enabled" to PipelineScheduleCreate payload<commit_after>import Relay from 'react-relay/classic';

class PipelineScheduleCreate extends Relay.Mutation {
  static fragments = {
    pipeline: () => Relay.QL`
      fragment on Pipeline {
        id
      }
    `
  }

  getMutation() {
    return Relay.QL`
      mutation {
        pipelineScheduleCreate
      }
    `;
  }

  getFatQuery() {
    return Relay.QL`
      fragment on PipelineScheduleCreatePayload {
        pipelineScheduleEdge,
        pipeline {
          schedules {
            count
          }
        }
      }
    `;
  }

  getConfigs() {
    return [{
      type: 'REQUIRED_CHILDREN',
      children: [
        Relay.QL`
          fragment on PipelineScheduleCreatePayload {
            pipelineScheduleEdge {
              node {
                uuid
                pipeline {
                  schedules {
                    count
                  }
                }
              }
            }
          }
        `
      ]
    }, {
      type: 'RANGE_ADD',
      parentName: 'pipeline',
      parentID: this.props.pipeline.id,
      connectionName: 'schedules',
      edgeName: 'pipelineScheduleEdge',
      rangeBehaviors: {
        '': 'append'
      }
    }];
  }

  getVariables() {
    return {
      pipelineID: this.props.pipeline.id,
      cronline: this.props.cronline,
      label: this.props.label,
      message: this.props.message,
      commit: this.props.commit,
      branch: this.props.branch,
      enabled: this.props.enabled,
      env: this.props.env
    };
  }
}

export default PipelineScheduleCreate;
<|endoftext|>


<commit_before>var peerflix = require('peerflix'),
    child_process = require('child_process'),
    address = require('network-address'),
    numeral = require('numeral'),
    readTorrent = require('read-torrent'),
    magnet = require('magnet-uri');

// Minimum percentage to open video
var MIN_PERCENTAGE_LOADED = 0.5;

// Minimum bytes loaded to open video
var MIN_SIZE_LOADED = 10 * 1024 * 1024;

// Video array (store open videos)
var videos = [];

// Last video id loaded
var videos_last_id = 0;

// Format bytes to readable format
var bytes = function(num) {
    return numeral(num).format('0.0b');
};

function randomPortNumber(min,max) {
    return Math.floor(Math.random()*(max-min+1)+min);
}

var playTorrent = function (torrent, callback, statsCallback) {

    var infoHash = torrent.infoHash;

    // Create a unique file to cache the video (with a microtimestamp) to prevent read conflicts
    var tmpFilename = torrent.infoHash;
    tmpFilename = tmpFilename.replace(/([^a-zA-Z0-9-_])/g, '_') +'-'+ (new Date()*1) +'.mp4';
    var tmpFile = path.join(tmpDir, tmpFilename);

    // Set new video ID
    videos_last_id++;
    var video_id = videos_last_id;

    var engine = peerflix(torrent, {
        // Set the custom temp file
        path: tmpFile,
        buffer: (1.5 * 1024 * 1024).toString(),
        port: randomPortNumber(49152,65534),
        connections: 100
    });
    var hotswaps = 0;

    var started = Date.now();
    var wires = engine.swarm.wires;
    var swarm = engine.swarm;

    var loadedTimeout, fire_start = false, timeout = false;

    var active = function(wire) {
        return !wire.peerChoking;
    };

    engine.on('uninterested', function() {
        engine.swarm.pause();
    });

    engine.on('interested', function() {
        engine.swarm.resume();
    });

    engine.server.on('listening', function() {
        var href = 'http://'+address()+':'+engine.server.address().port+'/';
        loadedTimeout ? clearTimeout(loadedTimeout) : null;

        var runtime = Math.floor((Date.now() - started) / 1000);

        var checkLoadingStats = function () {

            var now = swarm.downloaded,
                total = engine.torrent.length,
                targetLoadedSize = MIN_SIZE_LOADED > total ? total : MIN_SIZE_LOADED,
                targetLoadedPercent = MIN_PERCENTAGE_LOADED * total / 100.0,

                targetLoaded = Math.max(targetLoadedPercent, targetLoadedSize),

                percent = now / targetLoaded * 100.0;

            var runtime = Math.floor((Date.now() - started) / 1000);

            if (now > targetLoaded && !fire_start) {
                if (typeof callback === 'function') {
                    callback(false, href);
                }
                fire_start = true;
            }
            if (now < total && !timeout) {
                // If download choked (no peers), send timeout for restart
                if (runtime > 40 && !wires.length) {
                    timeout = true;
                }
                // Send streaming stats callback
                typeof statsCallback == 'function' ? statsCallback( percent, fire_start, bytes(swarm.downloadSpeed()), swarm.wires.filter(active).length, wires.length, timeout, video_id) : null;
                loadedTimeout = setTimeout(checkLoadingStats, 500);
            }
        };
        checkLoadingStats();

    });

    engine.server.once('error', function() {
        if (loadedTimeout) { clearTimeout(loadedTimeout); }
        engine.server.listen(0);
    });

    engine.on('ready', function() {
        engine.server.listen(8284);
    });

    engine.on('error', function() {
        if (loadedTimeout) { clearTimeout(loadedTimeout); }
        if (typeof callback === 'function') {
            callback(true, null);
        }
    })

    // Close video listener
    $(document).on('closeVideo'+video_id, function() {
        if (loadedTimeout) { clearTimeout(loadedTimeout); }

        // Delete video ID
        for (var i in window.videos) {
            if (window.videos[i].id == video_id) {
                window.videos.splice(i, 1);
            }
        }

        swarm.destroy();

        // Clean dir in tmp cache
        if (fs.existsSync(tmpFile)) {
            fs.readdir(tmpFile, function(err, files) {
                if (!err) {
                    if (files.length > 0) {
                        for (var i in files) {
                            var filePath = tmpDir + files[i];
                            fs.stat(filePath, function(err, stats) {
                                if (!err) {
                                    if (stats.isFile()) {
                                        fs.unlink(filePath);
                                    }
                                }
                            });
                        }
                    }
                }
            })
        }

        $(document).off('closeVideo'+video_id);

        delete engine;
    });

    // Add new video to array
    videos.push({id: video_id, engine: engine});

};<commit_msg>Fix listening port<commit_after>var peerflix = require('peerflix'),
    child_process = require('child_process'),
    address = require('network-address'),
    numeral = require('numeral'),
    readTorrent = require('read-torrent'),
    magnet = require('magnet-uri');

// Minimum percentage to open video
var MIN_PERCENTAGE_LOADED = 0.5;

// Minimum bytes loaded to open video
var MIN_SIZE_LOADED = 10 * 1024 * 1024;

// Video array (store open videos)
var videos = [];

// Last video id loaded
var videos_last_id = 0;

// Format bytes to readable format
var bytes = function(num) {
    return numeral(num).format('0.0b');
};

function randomPortNumber(min,max) {
    return Math.floor(Math.random()*(max-min+1)+min);
}

var playTorrent = function (torrent, callback, statsCallback) {

    var infoHash = torrent.infoHash;

    // Create a unique file to cache the video (with a microtimestamp) to prevent read conflicts
    var tmpFilename = torrent.infoHash;
    tmpFilename = tmpFilename.replace(/([^a-zA-Z0-9-_])/g, '_') +'-'+ (new Date()*1) +'.mp4';
    var tmpFile = path.join(tmpDir, tmpFilename);

    // Set new video ID
    videos_last_id++;
    var video_id = videos_last_id;

    // Set random port
    var port = randomPortNumber(49152,65534);

    var engine = peerflix(torrent, {
        // Set the custom temp file
        path: tmpFile,
        buffer: (1.5 * 1024 * 1024).toString(),
        port: port,
        connections: 100
    });
    var hotswaps = 0;

    var started = Date.now();
    var wires = engine.swarm.wires;
    var swarm = engine.swarm;

    var loadedTimeout, fire_start = false, timeout = false;

    var active = function(wire) {
        return !wire.peerChoking;
    };

    engine.on('uninterested', function() {
        engine.swarm.pause();
    });

    engine.on('interested', function() {
        engine.swarm.resume();
    });

    engine.server.on('listening', function() {
        var href = 'http://'+address()+':'+engine.server.address().port+'/';
        loadedTimeout ? clearTimeout(loadedTimeout) : null;

        var runtime = Math.floor((Date.now() - started) / 1000);

        var checkLoadingStats = function () {

            var now = swarm.downloaded,
                total = engine.torrent.length,
                targetLoadedSize = MIN_SIZE_LOADED > total ? total : MIN_SIZE_LOADED,
                targetLoadedPercent = MIN_PERCENTAGE_LOADED * total / 100.0,

                targetLoaded = Math.max(targetLoadedPercent, targetLoadedSize),

                percent = now / targetLoaded * 100.0;

            var runtime = Math.floor((Date.now() - started) / 1000);

            if (now > targetLoaded && !fire_start) {
                if (typeof callback === 'function') {
                    callback(false, href);
                }
                fire_start = true;
            }
            if (now < total && !timeout) {
                // If download choked (no peers), send timeout for restart
                if (runtime > 40 && !wires.length) {
                    timeout = true;
                }
                // Send streaming stats callback
                typeof statsCallback == 'function' ? statsCallback( percent, fire_start, bytes(swarm.downloadSpeed()), swarm.wires.filter(active).length, wires.length, timeout, video_id) : null;
                loadedTimeout = setTimeout(checkLoadingStats, 500);
            }
        };
        checkLoadingStats();

    });

    engine.server.once('error', function() {
        if (loadedTimeout) { clearTimeout(loadedTimeout); }
        engine.server.listen(0);
    });

    engine.on('ready', function() {
        engine.server.listen(port);
    });

    engine.on('error', function() {
        if (loadedTimeout) { clearTimeout(loadedTimeout); }
        if (typeof callback === 'function') {
            callback(true, null);
        }
    })

    // Close video listener
    $(document).on('closeVideo'+video_id, function() {
        if (loadedTimeout) { clearTimeout(loadedTimeout); }

        // Delete video ID
        for (var i in window.videos) {
            if (window.videos[i].id == video_id) {
                window.videos.splice(i, 1);
            }
        }

        swarm.destroy();

        // Clean dir in tmp cache
        if (fs.existsSync(tmpFile)) {
            fs.readdir(tmpFile, function(err, files) {
                if (!err) {
                    if (files.length > 0) {
                        for (var i in files) {
                            var filePath = tmpDir + files[i];
                            fs.stat(filePath, function(err, stats) {
                                if (!err) {
                                    if (stats.isFile()) {
                                        fs.unlink(filePath);
                                    }
                                }
                            });
                        }
                    }
                }
            })
        }

        $(document).off('closeVideo'+video_id);

        delete engine;
    });

    // Add new video to array
    videos.push({id: video_id, engine: engine});

};<|endoftext|>


<commit_before>require('core/string');
include('helma/file');
importPackage(org.mozilla.javascript);
importClass(org.helma.repository.FileRepository);
importClass(org.helma.repository.FileResource);

/**
 * Get a list of resources with the given path prefix, optionally descending into subdirectories.
 * @param path {String} the base path
 * @param recursive {Boolean} whether to descend into subdirectories
 * @return an Array of Resource objects
 */
exports.getScriptResources = function(path, recursive) {
    var file = new File(path);
    var list;
    if (!file.exists()) {
        return [];
    } else if (file.isDirectory()) {
        list = new ScriptableList(new FileRepository(file).getResources(Boolean(recursive)));
    } else {
        list = [new FileResource(new java.io.File(path))];
    }
    return list.filter(function(resource) resource.name.endsWith(".js"));
}

/**
 * Parse a script resource and apply the visitorFunction to the script's nodes.
 * The function takes one argument which is a org.mozilla.javascript.ast.AstNode.
 * The function must return true to visit child nodes of the current node.
 * @param resource {Resource} an instance of org.helma.repository.Resource
 * @param visitorFunction {Function} the visitor function
 */
exports.parseScriptResource = function(resource, visitorFunction) {
    var ast = getParser().parse(resource.content, resource.name, 0);
    ast.visit(new org.mozilla.javascript.ast.NodeVisitor({
        visit: visitorFunction
    }));
}

exports.isName = function(node) {
    return node instanceof org.mozilla.javascript.ast.Name;
}

exports.getName = function(node) {
    return exports.isName(node) ? node.getString() : "";
}

function getParser() {
    var ce = new CompilerEnvirons();
    ce.setRecordingComments(true);
    ce.setRecordingLocalJsDocComments(true)
    ce.initFromContext(Context.getCurrentContext());
    return new Parser(ce, null);
}
<commit_msg>Add some JSDoc parsing smarts, partially borrowed from jsdoc-toolkit, partially self-grown.<commit_after>require('core/string');
include('helma/file');
importPackage(org.mozilla.javascript);
importClass(org.helma.repository.FileRepository);
importClass(org.helma.repository.FileResource);

/**
 * Get a list of resources with the given path prefix, optionally descending into subdirectories.
 * @param path {String} the base path
 * @param recursive {Boolean} whether to descend into subdirectories
 * @return an Array of Resource objects
 */
exports.getScriptResources = function(path, recursive) {
    var file = new File(path);
    var list;
    if (!file.exists()) {
        return [];
    } else if (file.isDirectory()) {
        list = new ScriptableList(new FileRepository(file).getResources(Boolean(recursive)));
    } else {
        list = [new FileResource(new java.io.File(path))];
    }
    return list.filter(function(resource) resource.name.endsWith(".js"));
}

/**
 * Parse a script resource and apply the visitorFunction to the script's nodes.
 * The function takes one argument which is a org.mozilla.javascript.ast.AstNode.
 * The function must return true to visit child nodes of the current node.
 * @param resource {Resource} an instance of org.helma.repository.Resource
 * @param visitorFunction {Function} the visitor function
 */
exports.parseScriptResource = function(resource, visitorFunction) {
    var ast = getParser().parse(resource.content, resource.name, 0);
    ast.visit(new org.mozilla.javascript.ast.NodeVisitor({
        visit: visitorFunction
    }));
}

/**
 * Remove slash-star comment wrapper from a raw comment string.
 * @type String
 */
exports.unwrapComment = function(/**String*/comment) {
    return comment ? comment.replace(/(^\/\*\*|\*\/$)/g, "").replace(/^\s*\* ?/gm, "") : "";
}

/**
 * Parse a JSDoc comment into an array of tags, with tags being represented
 * as [tagname, tagtext].
 * @param {String} comment the raw JSDoc comment
 * @return {Array} an array of tags.
 */
exports.extractTags = function(/**String*/comment) {
    if (comment.startsWith("/**")) {
        comment = exports.unwrapComment(comment);
    }
    var tags = comment.split(/(^|[\r\n])\s*@/)
            .filter(function($){return $.match(/\S/)});
    return tags.map(function(tag, idx) {
        if (idx == 0 && !comment.startsWith('@')) {
            return ['desc', tag.trim()];
        } else {
            var space = tag.search(/\s/);
            return space > -1 ?
                   [tag.substring(0, space), tag.substring(space + 1).trim()] :
                   [tag, ''];
        }
    })
}

/**
 * Utility function to test whether a node is a Name node
 * (a node of type org.mozilla.javascript.ast.Name)
 * @param node {Object} an AST node
 * @return {Boolean} true if node is a name node
 */
exports.isName = function(node) {
    return node instanceof org.mozilla.javascript.ast.Name;
}

/**
 * Utility function to get the name value of a node, or the empty
 * string if it is not a name node.
 * @param node an AST node
 * @return {String} the name value of the node
 */
exports.getName = function(node) {
    return exports.isName(node) ? node.getString() : "";
}

function getParser() {
    var ce = new CompilerEnvirons();
    ce.setRecordingComments(true);
    ce.setRecordingLocalJsDocComments(true)
    ce.initFromContext(Context.getCurrentContext());
    return new Parser(ce, null);
}
<|endoftext|>


<commit_before>var Future = Npm.require("fibers/future");
var fogbugz = Npm.require("fogbugz.js");

function logfail(p){
	p.fail(function(err){
		console.log('[fogbugz] error:', err);
	});
	return p;
}

// creates new fogbugz clients using context from given user
function fbc(user){
	var service = user.services.fogbugz;
	var p = fogbugz({
		url: service.endpoint,
		token: service.token,
		// verbose: true // uncomment for verbose logging of fogbugz requests
	});
	logfail(p);
	return p;
}

var ItemStatus = {
	active: 'active',
	doing: 'doing',
	review: 'review',
	test: 'test',
	done: 'done'
};

virtual_user_patterns = ['team', 'qa', 'dev'].map(function(s){
	return new RegExp("\\s*" + s + "\\s*", "i");
});

function is_virtual_user(name){
	return virtual_user_patterns.some(function(e){
		return e.test(name);
	});
}

// maps fogbugz case status to kanban invariant status
function resolveStatus(it) {
	var s = ((it.status || {}).name || '').toLowerCase();
	if (s.indexOf('review') >= 0) return 'review';
	if (s.indexOf('resolved') >= 0) return 'test';
	if (s.indexOf('close') >= 0) return 'done';
	// TODO detect virtual users using data from fogbugz
	var assignee = (it.assignee.name || '').toLowerCase();
	var isTeam = is_virtual_user(assignee);
	return isTeam ? 'active' : 'doing';
}

// maps fogbugz case category to kanban invariant category
function resolveCategory(it) {
	var s =((it.category || {}).name || '').toLowerCase();
	if (s.indexOf('feature') >= 0) return 'feature';
	if (s.indexOf('schedule item') >= 0) return 'task';
	if (s.indexOf('inquiry') >= 0) return 'inquiry';
	if (s.indexOf('review') >= 0) return 'code-review';
	if (s.indexOf('requirement') >= 0) return 'requirement';
	return 'bug';
}

function logerror(promise){
	promise.fail(function(error){
		console.log('fogbugz error: %s', error);
	});
	return promise;
}

function edit(user, data){
	return fbc(user).then(function(client){
		return logerror(client.edit(data));
	});
}

function close(user, id){
	return fbc(user).then(function(client){
		return logerror(client.close(id, 'verified'));
	});
}

function toTime(d){
	return d ? d.getTime() : null;
}

function toWorkItem(it, board) {
	return {
		// link with fogbugz service
		service: board.service,
		board: board.name,
		id: it.id,
		priority: it.priority.id,
		title: it.title,
		assignee: it.assignee,
		category: resolveCategory(it),
		status: resolveStatus(it),
		tags: it.tags,
		// dates
		opened: toTime(it.opened),
		resolved: toTime(it.resolved),
		closed: toTime(it.closed),
		events: it.events
	};
}

function toBoard(it, user){
	var service = user.services.fogbugz;
	// TODO customize if needed
	return _.extend({}, it, {
		// link board with fogbugz service
		service: service.endpoint,
		columns: [
			{name: 'Open', status: 'active'},
			{name: 'In progress', status: 'doing'},
			{name: 'On review', status: 'review'},
			{name: 'In testing', status: 'test'},
			{name: 'Verified', status: 'done'}
		]
	});
}

FogBugzService = {
	// Creates fogbugz client with specified options
	//
	// @param options {Object} (required) See https://npmjs.org/package/fogbugz.js
	// @returns {Object} The instance of fogbugz client
	connect: function (options) {
		// return toFuture(fogbugz, null)(options);
		return fogbugz(options);
	},

	toWorkItem: toWorkItem,

	// get available boards
	fetchBoards: function(user){
		return fbc(user).then(function(client){
			return client.milestones(true);
		}).then(function(list){
			var now = moment(new Date());
			return list.filter(function(m){
				if (!m.end) return false;
				var end = moment(m.end);
				var d = end.diff(now, 'days');
				// filter out past milestones including prev sprints
				return d >= -14;
			}).map(function(it){
				return toBoard(it, user);
			});
		});
	},

	// gets items for specified board
	fetchItems: function(user, board){
		return fbc(user).then(function(client){
			return client.milestone(board).cases();
		}).then(function(list){
			return list.map(function(it){
				return toWorkItem(it, board);
			});
		});
	},

	// fetch fogbugz user
	users: function(user){
		// todo filter unnecessary fields
		return fbc(user).then(function(client){
			return client.users();
		});
	},

	// adds comment to given case
	comment: function(user, itemId, text){
		return fbc(user).then(function(client){
			return client.log(itemId, text);
		});
	},

	// TODO remove hardcoded statuses, move to config
	updateStatus: function(user, item, status){

		// TODO customizable workflow.json
		var data;
		var assignee = user.username;

		switch (status){
			// assign to team or to the person who worked on the item
			case ItemStatus.active:
				assignee = Meteor.settings.public.team;
				data = {
					id: item.id,
					// TODO back to person when case is reactivated
					user: assignee,
					status: 'Active',
					comment: 'taken'
				};
				break;

			case ItemStatus.doing:
				data = {
					id: item.id,
					user: user.profile.id,
					status: 'Active',
					comment: 'taken'
				};
				break;

			case ItemStatus.review:
				assignee = Meteor.settings.public.team;
				data = {
					id: item.id,
					user: assignee,
					status: 'On Review',
					comment: 'pending code review'
				};
				break;

			case ItemStatus.test:
				assignee = Meteor.settings.public.qateam;
				data = {
					id: item.id,
					user: assignee,
					status: 'Resolved',
					comment: 'in testing'
				};
				break;

			case ItemStatus.done:
				item.status = 'done';
				close(user, item.id);
				return item;

			default:
				throw new Error('Invalid status ' + status);
		}

		item.status = status;
		if (item.assignee) {
			item.assignee.name = assignee;
		}

		edit(user, data);

		return item;
	}
};<commit_msg>using ItemStatus (micro refactoring)<commit_after>var Future = Npm.require("fibers/future");
var fogbugz = Npm.require("fogbugz.js");

function logfail(p){
	p.fail(function(err){
		console.log('[fogbugz] error:', err);
	});
	return p;
}

// creates new fogbugz clients using context from given user
function fbc(user){
	var service = user.services.fogbugz;
	var p = fogbugz({
		url: service.endpoint,
		token: service.token,
		// verbose: true // uncomment for verbose logging of fogbugz requests
	});
	logfail(p);
	return p;
}

var ItemStatus = {
	active: 'active',
	doing: 'doing',
	review: 'review',
	test: 'test',
	done: 'done'
};

virtual_user_patterns = ['team', 'qa', 'dev'].map(function(s){
	return new RegExp("\\s*" + s + "\\s*", "i");
});

function is_virtual_user(name){
	return virtual_user_patterns.some(function(e){
		return e.test(name);
	});
}

// maps fogbugz case status to kanban invariant status
function resolveStatus(it) {
	var s = ((it.status || {}).name || '').toLowerCase();
	// TODO use status map
	if (s.indexOf('review') >= 0) return ItemStatus.review;
	if (s.indexOf('resolved') >= 0) return ItemStatus.test;
	if (s.indexOf('close') >= 0) return ItemStatus.done;
	// TODO detect virtual users using data from fogbugz
	var assignee = (it.assignee.name || '').toLowerCase();
	var isTeam = is_virtual_user(assignee);
	return isTeam ? 'active' : 'doing';
}

// maps fogbugz case category to kanban invariant category
function resolveCategory(it) {
	var s =((it.category || {}).name || '').toLowerCase();
	if (s.indexOf('feature') >= 0) return 'feature';
	if (s.indexOf('schedule item') >= 0) return 'task';
	if (s.indexOf('inquiry') >= 0) return 'inquiry';
	if (s.indexOf('review') >= 0) return 'code-review';
	if (s.indexOf('requirement') >= 0) return 'requirement';
	return 'bug';
}

function logerror(promise){
	promise.fail(function(error){
		console.log('fogbugz error: %s', error);
	});
	return promise;
}

function edit(user, data){
	return fbc(user).then(function(client){
		return logerror(client.edit(data));
	});
}

function close(user, id){
	return fbc(user).then(function(client){
		return logerror(client.close(id, 'verified'));
	});
}

function toTime(d){
	return d ? d.getTime() : null;
}

function toWorkItem(it, board) {
	return {
		// link with fogbugz service
		service: board.service,
		board: board.name,
		id: it.id,
		priority: it.priority.id,
		title: it.title,
		assignee: it.assignee,
		category: resolveCategory(it),
		status: resolveStatus(it),
		tags: it.tags,
		// dates
		opened: toTime(it.opened),
		resolved: toTime(it.resolved),
		closed: toTime(it.closed),
		events: it.events
	};
}

function toBoard(it, user){
	var service = user.services.fogbugz;
	// TODO customize if needed
	return _.extend({}, it, {
		// link board with fogbugz service
		service: service.endpoint,
		columns: [
			{name: 'Open', status: 'active'},
			{name: 'In progress', status: 'doing'},
			{name: 'On review', status: 'review'},
			{name: 'In testing', status: 'test'},
			{name: 'Verified', status: 'done'}
		]
	});
}

FogBugzService = {
	// Creates fogbugz client with specified options
	//
	// @param options {Object} (required) See https://npmjs.org/package/fogbugz.js
	// @returns {Object} The instance of fogbugz client
	connect: function (options) {
		// return toFuture(fogbugz, null)(options);
		return fogbugz(options);
	},

	toWorkItem: toWorkItem,

	// get available boards
	fetchBoards: function(user){
		return fbc(user).then(function(client){
			return client.milestones(true);
		}).then(function(list){
			var now = moment(new Date());
			return list.filter(function(m){
				if (!m.end) return false;
				var end = moment(m.end);
				var d = end.diff(now, 'days');
				// filter out past milestones including prev sprints
				return d >= -14;
			}).map(function(it){
				return toBoard(it, user);
			});
		});
	},

	// gets items for specified board
	fetchItems: function(user, board){
		return fbc(user).then(function(client){
			return client.milestone(board).cases();
		}).then(function(list){
			return list.map(function(it){
				return toWorkItem(it, board);
			});
		});
	},

	// fetch fogbugz user
	users: function(user){
		// todo filter unnecessary fields
		return fbc(user).then(function(client){
			return client.users();
		});
	},

	// adds comment to given case
	comment: function(user, itemId, text){
		return fbc(user).then(function(client){
			return client.log(itemId, text);
		});
	},

	// TODO remove hardcoded statuses, move to config
	updateStatus: function(user, item, status){

		// TODO customizable workflow.json
		var data;
		var assignee = user.username;

		switch (status){
			// assign to team or to the person who worked on the item
			case ItemStatus.active:
				assignee = Meteor.settings.public.team;
				data = {
					id: item.id,
					// TODO back to person when case is reactivated
					user: assignee,
					status: 'Active',
					comment: 'taken'
				};
				break;

			case ItemStatus.doing:
				data = {
					id: item.id,
					user: user.profile.id,
					status: 'Active',
					comment: 'taken'
				};
				break;

			case ItemStatus.review:
				assignee = Meteor.settings.public.team;
				data = {
					id: item.id,
					user: assignee,
					status: 'On Review',
					comment: 'pending code review'
				};
				break;

			case ItemStatus.test:
				assignee = Meteor.settings.public.qateam;
				data = {
					id: item.id,
					user: assignee,
					status: 'Resolved',
					comment: 'in testing'
				};
				break;

			case ItemStatus.done:
				item.status = 'done';
				close(user, item.id);
				return item;

			default:
				throw new Error('Invalid status ' + status);
		}

		item.status = status;
		if (item.assignee) {
			item.assignee.name = assignee;
		}

		edit(user, data);

		return item;
	}
};<|endoftext|>


<commit_before>
define([
  'jquery',
  'underscore',
  'backbone',
  'collections/topic',
  'text!templates/topic.html',
  'helpers/nav',
  'views/entry',
  'bootstrap'
], function ($, _, Backbone, TopicCollection,
             TopicTemplate, NavHelper, EntryView) {

  var SidebarView = Backbone.View.extend ({
    el: '#main',

    initialize: function () {
      this.topicCollection = new TopicCollection ();

      NavHelper.initialize ();
    },

    render: function (order, external_url) {
      this.loadResults (order, external_url);
      if (!this.entryview) { new EntryView (); this.entryview = true; }
    },

    loadResults: function (order, external_url) {

      var that = this;

      this.isLoading = true;

      // external_url used for new pages
      if (external_url) {
        this.topicCollection.external_url = external_url;
      }

      // FIXME: need to check valid orders
      if (order) {
        this.topicCollection.order = order;
      }

      this.topicCollection.fetch ({

        success: function (entries) {

          if (external_url) {

            // clear page
            $(that.el).html ('');
            $(that.el).scrollTop ();

            NavHelper.setTitle (that.topicCollection.title, {
                                  title: that.topicCollection.title,
                                  external_url: external_url
                                });

            // bind refresh button
            NavHelper.bindRefresh (that);
          }

          // redirect to DESC order if there is no entry to dipslay
          if (order == 2 &&
              entries.length == 0) {
            location.href = '#t/1/' + that.topicCollection.external_url;
            return;
          }

          $(that.el).append (_.template (TopicTemplate,
                                         {entries: entries.toJSON ()}));

          that.isLoading = false;

          that.checkScroll ();

        },

      });

    },

    events: {
      'scroll': 'checkScroll'
    },

    checkScroll: function () {

      if (!this.isLoading &&
          !this.topicCollection.lastPage () &&
          this.el.scrollTop + this.el.clientHeight + 200 >
          this.el.scrollHeight) {
        this.topicCollection.nextPage ();
        this.loadResults ();
      }

      if (!this.isLoading &&
          this.topicCollection.lastPage () &&
          this.topicCollection.order == 2 &&
          !this.showAllEntries) {
        this.showAllEntries = true;
        $('#main').append (
          $('<a>').attr ('class', 'btn btn-default btn-lg btn-block')
                  .attr ('href', '#t/0/' + this.topicCollection.external_url)
                  .text ('Tüm entryleri göster')

        );
     }

    },

    refresh: function () {
      var order = this.topicCollection.order;
      var external_url = this.topicCollection.external_url;
      this.topicCollection.reset ();
      this.topicCollection.firstPage ();
      this.loadResults (order, external_url);
    },

  });

  return SidebarView;

});
<commit_msg>redirect to desc order page<commit_after>
define([
  'jquery',
  'underscore',
  'backbone',
  'collections/topic',
  'text!templates/topic.html',
  'helpers/nav',
  'views/entry',
  'bootstrap'
], function ($, _, Backbone, TopicCollection,
             TopicTemplate, NavHelper, EntryView) {

  var SidebarView = Backbone.View.extend ({
    el: '#main',

    initialize: function () {
      this.topicCollection = new TopicCollection ();

      NavHelper.initialize ();
    },

    render: function (order, external_url) {
      this.loadResults (order, external_url);
      if (!this.entryview) { new EntryView (); this.entryview = true; }
    },

    loadResults: function (order, external_url) {

      var that = this;

      this.isLoading = true;

      // external_url used for new pages
      if (external_url) {
        this.topicCollection.external_url = external_url;
      }

      // FIXME: need to check valid orders
      if (order) {
        this.topicCollection.order = order;
      }

      this.topicCollection.fetch ({

        success: function (entries) {

          if (external_url) {

            // clear page
            $(that.el).html ('');
            $(that.el).scrollTop ();

            NavHelper.setTitle (that.topicCollection.title, {
                                  title: that.topicCollection.title,
                                  external_url: external_url
                                });

            // bind refresh button
            NavHelper.bindRefresh (that);
          }

          $(that.el).append (_.template (TopicTemplate,
                                         {entries: entries.toJSON ()}));

          that.isLoading = false;

          that.checkScroll ();

        },

        error: function () {

          // redirect to DESC order if there is no entry to dipslay
          if (order == 2)
            location.href = '#t/1/' + that.topicCollection.external_url;

        }

      });

    },

    events: {
      'scroll': 'checkScroll'
    },

    checkScroll: function () {

      if (!this.isLoading &&
          !this.topicCollection.lastPage () &&
          this.el.scrollTop + this.el.clientHeight + 200 >
          this.el.scrollHeight) {
        this.topicCollection.nextPage ();
        this.loadResults ();
      }

      if (!this.isLoading &&
          this.topicCollection.lastPage () &&
          this.topicCollection.order == 2 &&
          !this.showAllEntries) {
        this.showAllEntries = true;
        $('#main').append (
          $('<a>').attr ('class', 'btn btn-default btn-lg btn-block')
                  .attr ('href', '#t/0/' + this.topicCollection.external_url)
                  .text ('Tüm entryleri göster')

        );
     }

    },

    refresh: function () {
      var order = this.topicCollection.order;
      var external_url = this.topicCollection.external_url;
      this.topicCollection.reset ();
      this.topicCollection.firstPage ();
      this.loadResults (order, external_url);
    },

  });

  return SidebarView;

});
<|endoftext|>