<commit_before>(function ($) {

    $.widget('map2gpx.map', {
        options: {
            leafletOptions: {},

            controls: {
                searchEngine: {
                    show: true,
                    leafletOptions: {
                        displayAdvancedSearch: false,
                    },
                },
                minimap: {
                    show: true,
                    leafletOptions: {
                        position: 'bottomleft',
                        zoomLevelOffset: -4,
                    },
                },
                layerSwitcher: {
                    show: true,
                    leafletOptions: {
                        collapsed: false,
                    },
                },
                scale: {
                    show: true,
                    leafletOptions: {
                        imperial: false,
                        position: 'bottomright',
                    },
                },
                help: {
                    show: true,
                },
            },
        },

        getMap: function () {
            return this.map;
        },

        getTrack: function () {
            return this.track;
        },

        getMode: function () {
            return this.mode;
        },

        _buildEventData: function () {
            return { mode: this.mode };
        },

        _setMode: function (mode) {
            this.mode = mode;
            this._trigger('modechanged', null, this._buildEventData());
        },

        _onCreated: function () {
            this.element.on('mapmodechanged', (e) => {
                this.map.doubleClickZoom.setEnabled((this.mode === null));
            });
            this.map.on('dblclick', (e) => this._addMarker.call(this, e));

            this._initializeLayers();
            if (this.options.controls.searchEngine.show)
                this._initializeSearchEngine();
            if (this.options.controls.minimap.show)
                this._initializeMinimap();
            if (this.options.controls.layerSwitcher.show)
                this._initializeLayerSwitcher();
            if (this.options.controls.scale.show)
                this._initializeScale();

            this._initializeTraceButtons();
            this._initializeExportButtons();
            this._initializeImportButtons();
            this._initializeHelpButtons();

            this._trigger('created', null, {});
            this._trigger('statechanged', null, this._buildEventData());

            $.when.apply($, this.layers.promises).done(() => {
                this._trigger('loaded', null, {});
            });
        },

        _initializeLayers: function () {
            const _this = this;

            this.layers.photos = L.geoportalLayer.WMTS({
                layer: 'ORTHOIMAGERY.ORTHOPHOTOS',
                apiKey: keyIgn,
            }).addTo(this.map);
            this.layers.promises.push($.Deferred(function () {
                _this.layers.photos.once('load', this.resolve);
            }));

            // Don't monitor load event, because we don't display this layer (thus, never fires)
            this.layers.slopes =  L.geoportalLayer.WMTS({
                layer: 'GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN',
                apiKey: keyIgn,
            }, {
                opacity: 0.25,
            }).addTo(this.map);

            this.layers.maps = L.geoportalLayer.WMTS({
                layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
                apiKey: keyIgn,
            }, {
                opacity: 0.25,
            }).addTo(this.map);
            this.layers.promises.push($.Deferred(function () {
                _this.layers.maps.once('load', this.resolve);
            }));

            let outOfRangeDrop;
            this.map.on('zoomend', () => {
                const currentZoom = this.map.getZoom();

                let outOfRange;
                let $outOfRangeTarget;
                if (this.map.hasLayer(this.layers.photos) && (this.layers.photos.options.minZoom > currentZoom || this.layers.photos.options.maxZoom < currentZoom)) {
                    outOfRange = 'Photographies aériennes';
                    $outOfRangeTarget = $('.GPlayerSwitcher_layer:eq(2)');
                } else if (this.map.hasLayer(this.layers.maps) && (this.layers.maps.options.minZoom > currentZoom || this.layers.maps.options.maxZoom < currentZoom)) {
                    outOfRange = 'Cartes IGN';
                    $outOfRangeTarget = $('.GPlayerSwitcher_layer:eq(0)');
                } else if (this.map.hasLayer(this.layers.slopes) && (this.layers.slopes.options.minZoom > currentZoom || this.layers.slopes.options.maxZoom < currentZoom)) {
                    outOfRange = 'Carte des pentes';
                    $outOfRangeTarget = $('.GPlayerSwitcher_layer:eq(1)');
                }

                if (outOfRange !== undefined && outOfRangeDrop === undefined) {
                    outOfRangeDrop = new Drop({
                        target: $outOfRangeTarget[0],
                        classes: 'drop-theme-arrows',
                        position: 'left middle',
                        constrainToWindow: false,
                        constrainToScrollParent: false,
                        openOn: null,
                        content: 'La couche "' + outOfRange + '" n\'est pas disponible à ce niveau de zoom',
                    });
                    outOfRangeDrop.open();
                    $(outOfRangeDrop.content).on('click', function () {
                        outOfRangeDrop.destroy();
                        outOfRangeDrop = null;
                    });
                } else if (outOfRange === undefined && outOfRangeDrop !== undefined && outOfRangeDrop !== null) {
                    outOfRangeDrop.destroy();
                    outOfRangeDrop = null;
                }
            });
        },

        _initializeSearchEngine: function () {
            L.geoportalControl.SearchEngine(this.options.controls.searchEngine.leafletOptions).addTo(this.map);
        },

        _initializeMinimap: function () {
            const _this = this;

            this.layers.minimap = L.geoportalLayer.WMTS({
                layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
                apiKey: keyIgn,
            });
            this.layers.promises.push($.Deferred(function () {
                _this.layers.minimap.once('load', this.resolve);
            }));

            new L.Control.MiniMap(this.layers.minimap, this.options.controls.minimap.leafletOptions).addTo(this.map);
        },

        _initializeLayerSwitcher: function () {
            let layerSwitcher = L.geoportalControl.LayerSwitcher(this.options.controls.layerSwitcher.leafletOptions);
            this.map.addControl(layerSwitcher);
            layerSwitcher.setVisibility(this.layers.slopes, false);
            $('.GPlayerRemove').remove();
        },

        _initializeScale: function () {
            L.control.scale(this.options.controls.scale.leafletOptions).addTo(this.map);
        },

        _initializeTraceButtons: function () {
            let automatedBtn = L.easyButton({
                id: 'btn-autotrace',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-map-signs',
                        title: 'Tracer automatiquement l\'itinéraire',
                        onClick: (btn, map) => this._setMode('auto'),
                    }, {
                        stateName: 'active',
                        icon: 'fa-map-signs',
                        title: 'Tracer automatiquement l\'itinéraire',
                        onClick: (btn, map) => this._setMode(null),
                    },
                ],
            });
            this.element.on('mapmodechanged mapstatechanged', (e) => {
                if (this.mode == 'auto') {
                    automatedBtn.state('active');
                    automatedBtn.enable();
                } else {
                    automatedBtn.state('loaded');
                    automatedBtn.setEnabled(!this.track.isImport());
                }
            });

            let lineBtn = L.easyButton({
                id: 'btn-straighttrace',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-location-arrow',
                        title: 'Tracer l\'itinéraire en ligne droite',
                        onClick: (btn, map) => this._setMode('straight'),
                    }, {
                        stateName: 'active',
                        icon: 'fa-location-arrow',
                        title: 'Tracer l\'itinéraire en ligne droite',
                        onClick: (btn, map) => this._setMode(null),
                    },
                ],
            });
            this.element.on('mapmodechanged mapstatechanged', (e) => {
                if (this.mode == 'straight') {
                    lineBtn.state('active');
                    lineBtn.enable();
                } else {
                    lineBtn.state('loaded');
                    lineBtn.setEnabled(!this.track.isImport());
                }
            });

            let closeLoop = L.easyButton({
                id: 'btn-closeloop',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-magic',
                        title: 'Fermer la boucle',
                        onClick: (btn, map) => {
                            if (this.track.hasMarkers(1)) {
                                this._addMarker({ latlng: this.track.getFirstMarker().getLatLng() });
                            }
                        },
                    },
                ],
            });
            this.element.on('mapmodechanged mapcomputingchanged mapstatechanged', (e) => {
                closeLoop.setEnabled((this.mode !== null && this.track.hasRoutes() && !this.track.isImport() && !this.track.isLoop()));
            });

            L.easyBar([automatedBtn, lineBtn, closeLoop]).addTo(this.map);
        },

        _initializeExportButtons: function () {
            const _this = this;

            let exportPopup = L.popup().setContent(L.DomUtil.get('form-export'));
            let exportButton = L.easyButton({
                id: 'btn-export',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-cloud-download',
                        title: 'Exporter',
                        onClick: (btn, map) => {
                            const bounds = this.track.getBounds();

                            this.map.flyToBounds(bounds, { padding: [50, 50] });
                            exportPopup.setLatLng(bounds.getCenter()).openOn(this.map);
                        },
                    }, {
                        stateName: 'computing',
                        icon: 'fa-spinner fa-pulse',
                        title: 'Exporter (calcul en cours...)',
                    },
                ],
            }).addTo(this.map);

            $('#export-gpx-button').click(function () {
                const $btn = $(this);
                $btn.attr('disabled', 'disabled');
                _this.track.exportGpx($('#export-filename').val());
                $btn.removeAttr('disabled');
            });

            $('#export-kml-button').click(function () {
                const $btn = $(this);
                $btn.attr('disabled', 'disabled');
                _this.track.exportKml($('#export-filename').val());
                $btn.removeAttr('disabled');
            });

            this.element.on('mapcomputingchanged mapstatechanged', (e) => {
                if (e.computing) {
                    exportButton.state('computing');
                    exportButton.disable();
                } else {
                    exportButton.state('loaded');
                    exportButton.setEnabled(this.track.hasRoutes());
                }
            });
        },

        _initializeImportButtons: function () {
            const _this = this;

            let importPopup = L.popup().setContent(L.DomUtil.get('form-import'));
            let importButton = L.easyButton({
                id: 'btn-import',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-cloud-upload',
                        title: 'Importer',
                        onClick: (btn, map) => {
                            importPopup.setLatLng(this.map.getCenter()).openOn(this.map);

                            if (this.track.hasRoutes()) {
                                $('#import-gpx-status').html('Attention: l\'import va effacer l\'itinéraire existant!');
                            } else {
                                $('#import-gpx-status').text('');
                            }
                        },
                    }, {
                        stateName: 'computing',
                        icon: 'fa-spinner fa-pulse',
                        title: 'Importer (calcul en cours...)',
                    },
                ],
            });

            $('#import-gpx-button').click(function () {
                const $btn = $(this);
                const f = $('#import-gpx-file')[0].files[0];
                const interpolate = $('#import-gpx-interpolate').is(':checked');

                if (f == undefined) {
                    $('#import-gpx-status').text('Veuillez sélectionner un fichier');
                    return;
                }

                $btn.attr('disabled', 'disabled');

                $('body').startCompute(function (next) {
                    $.Queue.notify({ start: true, total: 1, status: 'Importation en cours...' });
                    _this.track.importGpx(f, interpolate).done(function () {
                        $btn.removeAttr('disabled');
                        _this._setMode(null);  // Disable any other tracing
                    }).fail(function () {
                        $('#import-gpx-status').text(this.error);
                        $btn.removeAttr('disabled');
                    }).progress(function (progress) {
                        $.Queue.notify(progress);
                        if (importPopup.isOpen()) {
                            _this.map.closePopup();
                        }
                    }).always(() => $('body').endCompute(next));
                });
            });

            let resetButton = L.easyButton({
                id: 'btn-reset',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-trash',
                        title: 'Effacer l\'itinéraire',
                        onClick: (btn, map) => {
                            this.track.clear();
                        },
                    }, {
                        stateName: 'computing',
                        icon: 'fa-spinner fa-pulse',
                        title: 'Effacer l\'itinéraire (calcul en cours...)',
                    },
                ],
            });

            L.easyBar([importButton, resetButton]).addTo(this.map);
            this.element.on('mapcomputingchanged mapstatechanged', (e) => {
                importButton.state(this.computing ? 'computing' : 'loaded');
                resetButton.state(this.computing ? 'computing' : 'loaded');

                importButton.setEnabled(!this.computing);
                resetButton.setEnabled(!this.computing && this.track.hasMarkers());
            });
        },

        _initializeHelpButtons: function () {
            const infoPopup = L.popup().setContent(L.DomUtil.get('about'));

            const infoBtn = L.easyButton({
                position: 'bottomright',
                states: [
                    {
                        icon: 'fa-info-circle',
                        onClick: (btn, map) => {
                            infoPopup.setLatLng(this.map.getCenter()).openOn(this.map);
                        },
                        title: 'A propos & crédits',
                    },
                ],
            });
            const helpBtn = L.easyButton({
                position: 'bottomright',
                states: [
                    {
                        icon: 'fa-question-circle',
                        onClick: (btn, map) => {
                            $.Shepherd.get(0).start(true);
                        },
                        title: 'Aide',
                    },
                ],
            });

            L.easyBar([infoBtn, helpBtn], { position: 'bottomright' }).addTo(this.map);
        },

        _create: function () {
            this.element.uniqueId();

            this.layers = {};
            this.layers.promises = [];
            this.mode = null;

            this.map = L.map(this.element.attr('id'), this.options.leafletOptions);

            this.track = L.track({ map: this.element });
            this.track.on('markerschanged', () => this._trigger('statechanged', null, this._buildEventData()));

            this.map.initView().done(() => this._onCreated());
        },

        _addMarker: function (e) {
            if (this.mode === null) {
                return;
            }

            const marker = L.Marker.routed(e.latlng.roundE8(), {
                riseOnHover: true,
                draggable: true,
                opacity: 0.5,
                color: (this.track.hasMarkers()) ? this.track.getLastMarker().getColorIndex() : this.track.getCurrentColor(),
                type: 'waypoint',
            });

            // Ignore this marker if same as previous
            if (this.track.hasMarkers() && this.track.getLastMarker().getLatLng().equals(marker.getLatLng()))
                return;

            marker.add(this.track);
        },
    });

})(jQuery);
<commit_msg>Hide help buttons if small screen (popups won't display correctly)<commit_after>(function ($) {

    $.widget('map2gpx.map', {
        options: {
            leafletOptions: {},

            controls: {
                searchEngine: {
                    show: true,
                    leafletOptions: {
                        displayAdvancedSearch: false,
                    },
                },
                minimap: {
                    show: true,
                    leafletOptions: {
                        position: 'bottomleft',
                        zoomLevelOffset: -4,
                    },
                },
                layerSwitcher: {
                    show: true,
                    leafletOptions: {
                        collapsed: false,
                    },
                },
                scale: {
                    show: true,
                    leafletOptions: {
                        imperial: false,
                        position: 'bottomright',
                    },
                },
                help: {
                    show: true,
                },
            },
        },

        getMap: function () {
            return this.map;
        },

        getTrack: function () {
            return this.track;
        },

        getMode: function () {
            return this.mode;
        },

        _buildEventData: function () {
            return { mode: this.mode };
        },

        _setMode: function (mode) {
            this.mode = mode;
            this._trigger('modechanged', null, this._buildEventData());
        },

        _onCreated: function () {
            this.element.on('mapmodechanged', (e) => {
                this.map.doubleClickZoom.setEnabled((this.mode === null));
            });
            this.map.on('dblclick', (e) => this._addMarker.call(this, e));

            this._initializeLayers();
            if (this.options.controls.searchEngine.show)
                this._initializeSearchEngine();
            if (this.options.controls.minimap.show)
                this._initializeMinimap();
            if (this.options.controls.layerSwitcher.show)
                this._initializeLayerSwitcher();
            if (this.options.controls.scale.show)
                this._initializeScale();

            this._initializeTraceButtons();
            this._initializeExportButtons();
            this._initializeImportButtons();
            if (this.options.controls.help.show)
                this._initializeHelpButtons();

            this._trigger('created', null, {});
            this._trigger('statechanged', null, this._buildEventData());

            $.when.apply($, this.layers.promises).done(() => {
                this._trigger('loaded', null, {});
            });
        },

        _initializeLayers: function () {
            const _this = this;

            this.layers.photos = L.geoportalLayer.WMTS({
                layer: 'ORTHOIMAGERY.ORTHOPHOTOS',
                apiKey: keyIgn,
            }).addTo(this.map);
            this.layers.promises.push($.Deferred(function () {
                _this.layers.photos.once('load', this.resolve);
            }));

            // Don't monitor load event, because we don't display this layer (thus, never fires)
            this.layers.slopes =  L.geoportalLayer.WMTS({
                layer: 'GEOGRAPHICALGRIDSYSTEMS.SLOPES.MOUNTAIN',
                apiKey: keyIgn,
            }, {
                opacity: 0.25,
            }).addTo(this.map);

            this.layers.maps = L.geoportalLayer.WMTS({
                layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
                apiKey: keyIgn,
            }, {
                opacity: 0.25,
            }).addTo(this.map);
            this.layers.promises.push($.Deferred(function () {
                _this.layers.maps.once('load', this.resolve);
            }));

            let outOfRangeDrop;
            this.map.on('zoomend', () => {
                const currentZoom = this.map.getZoom();

                let outOfRange;
                let $outOfRangeTarget;
                if (this.map.hasLayer(this.layers.photos) && (this.layers.photos.options.minZoom > currentZoom || this.layers.photos.options.maxZoom < currentZoom)) {
                    outOfRange = 'Photographies aériennes';
                    $outOfRangeTarget = $('.GPlayerSwitcher_layer:eq(2)');
                } else if (this.map.hasLayer(this.layers.maps) && (this.layers.maps.options.minZoom > currentZoom || this.layers.maps.options.maxZoom < currentZoom)) {
                    outOfRange = 'Cartes IGN';
                    $outOfRangeTarget = $('.GPlayerSwitcher_layer:eq(0)');
                } else if (this.map.hasLayer(this.layers.slopes) && (this.layers.slopes.options.minZoom > currentZoom || this.layers.slopes.options.maxZoom < currentZoom)) {
                    outOfRange = 'Carte des pentes';
                    $outOfRangeTarget = $('.GPlayerSwitcher_layer:eq(1)');
                }

                if (outOfRange !== undefined && outOfRangeDrop === undefined) {
                    outOfRangeDrop = new Drop({
                        target: $outOfRangeTarget[0],
                        classes: 'drop-theme-arrows',
                        position: 'left middle',
                        constrainToWindow: false,
                        constrainToScrollParent: false,
                        openOn: null,
                        content: 'La couche "' + outOfRange + '" n\'est pas disponible à ce niveau de zoom',
                    });
                    outOfRangeDrop.open();
                    $(outOfRangeDrop.content).on('click', function () {
                        outOfRangeDrop.destroy();
                        outOfRangeDrop = null;
                    });
                } else if (outOfRange === undefined && outOfRangeDrop !== undefined && outOfRangeDrop !== null) {
                    outOfRangeDrop.destroy();
                    outOfRangeDrop = null;
                }
            });
        },

        _initializeSearchEngine: function () {
            L.geoportalControl.SearchEngine(this.options.controls.searchEngine.leafletOptions).addTo(this.map);
        },

        _initializeMinimap: function () {
            const _this = this;

            this.layers.minimap = L.geoportalLayer.WMTS({
                layer: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
                apiKey: keyIgn,
            });
            this.layers.promises.push($.Deferred(function () {
                _this.layers.minimap.once('load', this.resolve);
            }));

            new L.Control.MiniMap(this.layers.minimap, this.options.controls.minimap.leafletOptions).addTo(this.map);
        },

        _initializeLayerSwitcher: function () {
            let layerSwitcher = L.geoportalControl.LayerSwitcher(this.options.controls.layerSwitcher.leafletOptions);
            this.map.addControl(layerSwitcher);
            layerSwitcher.setVisibility(this.layers.slopes, false);
            $('.GPlayerRemove').remove();
        },

        _initializeScale: function () {
            L.control.scale(this.options.controls.scale.leafletOptions).addTo(this.map);
        },

        _initializeTraceButtons: function () {
            let automatedBtn = L.easyButton({
                id: 'btn-autotrace',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-map-signs',
                        title: 'Tracer automatiquement l\'itinéraire',
                        onClick: (btn, map) => this._setMode('auto'),
                    }, {
                        stateName: 'active',
                        icon: 'fa-map-signs',
                        title: 'Tracer automatiquement l\'itinéraire',
                        onClick: (btn, map) => this._setMode(null),
                    },
                ],
            });
            this.element.on('mapmodechanged mapstatechanged', (e) => {
                if (this.mode == 'auto') {
                    automatedBtn.state('active');
                    automatedBtn.enable();
                } else {
                    automatedBtn.state('loaded');
                    automatedBtn.setEnabled(!this.track.isImport());
                }
            });

            let lineBtn = L.easyButton({
                id: 'btn-straighttrace',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-location-arrow',
                        title: 'Tracer l\'itinéraire en ligne droite',
                        onClick: (btn, map) => this._setMode('straight'),
                    }, {
                        stateName: 'active',
                        icon: 'fa-location-arrow',
                        title: 'Tracer l\'itinéraire en ligne droite',
                        onClick: (btn, map) => this._setMode(null),
                    },
                ],
            });
            this.element.on('mapmodechanged mapstatechanged', (e) => {
                if (this.mode == 'straight') {
                    lineBtn.state('active');
                    lineBtn.enable();
                } else {
                    lineBtn.state('loaded');
                    lineBtn.setEnabled(!this.track.isImport());
                }
            });

            let closeLoop = L.easyButton({
                id: 'btn-closeloop',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-magic',
                        title: 'Fermer la boucle',
                        onClick: (btn, map) => {
                            if (this.track.hasMarkers(1)) {
                                this._addMarker({ latlng: this.track.getFirstMarker().getLatLng() });
                            }
                        },
                    },
                ],
            });
            this.element.on('mapmodechanged mapcomputingchanged mapstatechanged', (e) => {
                closeLoop.setEnabled((this.mode !== null && this.track.hasRoutes() && !this.track.isImport() && !this.track.isLoop()));
            });

            L.easyBar([automatedBtn, lineBtn, closeLoop]).addTo(this.map);
        },

        _initializeExportButtons: function () {
            const _this = this;

            let exportPopup = L.popup().setContent(L.DomUtil.get('form-export'));
            let exportButton = L.easyButton({
                id: 'btn-export',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-cloud-download',
                        title: 'Exporter',
                        onClick: (btn, map) => {
                            const bounds = this.track.getBounds();

                            this.map.flyToBounds(bounds, { padding: [50, 50] });
                            exportPopup.setLatLng(bounds.getCenter()).openOn(this.map);
                        },
                    }, {
                        stateName: 'computing',
                        icon: 'fa-spinner fa-pulse',
                        title: 'Exporter (calcul en cours...)',
                    },
                ],
            }).addTo(this.map);

            $('#export-gpx-button').click(function () {
                const $btn = $(this);
                $btn.attr('disabled', 'disabled');
                _this.track.exportGpx($('#export-filename').val());
                $btn.removeAttr('disabled');
            });

            $('#export-kml-button').click(function () {
                const $btn = $(this);
                $btn.attr('disabled', 'disabled');
                _this.track.exportKml($('#export-filename').val());
                $btn.removeAttr('disabled');
            });

            this.element.on('mapcomputingchanged mapstatechanged', (e) => {
                if (e.computing) {
                    exportButton.state('computing');
                    exportButton.disable();
                } else {
                    exportButton.state('loaded');
                    exportButton.setEnabled(this.track.hasRoutes());
                }
            });
        },

        _initializeImportButtons: function () {
            const _this = this;

            let importPopup = L.popup().setContent(L.DomUtil.get('form-import'));
            let importButton = L.easyButton({
                id: 'btn-import',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-cloud-upload',
                        title: 'Importer',
                        onClick: (btn, map) => {
                            importPopup.setLatLng(this.map.getCenter()).openOn(this.map);

                            if (this.track.hasRoutes()) {
                                $('#import-gpx-status').html('Attention: l\'import va effacer l\'itinéraire existant!');
                            } else {
                                $('#import-gpx-status').text('');
                            }
                        },
                    }, {
                        stateName: 'computing',
                        icon: 'fa-spinner fa-pulse',
                        title: 'Importer (calcul en cours...)',
                    },
                ],
            });

            $('#import-gpx-button').click(function () {
                const $btn = $(this);
                const f = $('#import-gpx-file')[0].files[0];
                const interpolate = $('#import-gpx-interpolate').is(':checked');

                if (f == undefined) {
                    $('#import-gpx-status').text('Veuillez sélectionner un fichier');
                    return;
                }

                $btn.attr('disabled', 'disabled');

                $('body').startCompute(function (next) {
                    $.Queue.notify({ start: true, total: 1, status: 'Importation en cours...' });
                    _this.track.importGpx(f, interpolate).done(function () {
                        $btn.removeAttr('disabled');
                        _this._setMode(null);  // Disable any other tracing
                    }).fail(function () {
                        $('#import-gpx-status').text(this.error);
                        $btn.removeAttr('disabled');
                    }).progress(function (progress) {
                        $.Queue.notify(progress);
                        if (importPopup.isOpen()) {
                            _this.map.closePopup();
                        }
                    }).always(() => $('body').endCompute(next));
                });
            });

            let resetButton = L.easyButton({
                id: 'btn-reset',
                states: [
                    {
                        stateName: 'loaded',
                        icon: 'fa-trash',
                        title: 'Effacer l\'itinéraire',
                        onClick: (btn, map) => {
                            this.track.clear();
                        },
                    }, {
                        stateName: 'computing',
                        icon: 'fa-spinner fa-pulse',
                        title: 'Effacer l\'itinéraire (calcul en cours...)',
                    },
                ],
            });

            L.easyBar([importButton, resetButton]).addTo(this.map);
            this.element.on('mapcomputingchanged mapstatechanged', (e) => {
                importButton.state(this.computing ? 'computing' : 'loaded');
                resetButton.state(this.computing ? 'computing' : 'loaded');

                importButton.setEnabled(!this.computing);
                resetButton.setEnabled(!this.computing && this.track.hasMarkers());
            });
        },

        _initializeHelpButtons: function () {
            const infoPopup = L.popup().setContent(L.DomUtil.get('about'));

            const infoBtn = L.easyButton({
                position: 'bottomright',
                states: [
                    {
                        icon: 'fa-info-circle',
                        onClick: (btn, map) => {
                            infoPopup.setLatLng(this.map.getCenter()).openOn(this.map);
                        },
                        title: 'A propos & crédits',
                    },
                ],
            });
            const helpBtn = L.easyButton({
                position: 'bottomright',
                states: [
                    {
                        icon: 'fa-question-circle',
                        onClick: (btn, map) => {
                            $.Shepherd.get(0).start(true);
                        },
                        title: 'Aide',
                    },
                ],
            });

            L.easyBar([infoBtn, helpBtn], { position: 'bottomright' }).addTo(this.map);
        },

        _create: function () {
            this.element.uniqueId();

            this.layers = {};
            this.layers.promises = [];
            this.mode = null;

            this.map = L.map(this.element.attr('id'), this.options.leafletOptions);

            this.track = L.track({ map: this.element });
            this.track.on('markerschanged', () => this._trigger('statechanged', null, this._buildEventData()));

            this.map.initView().done(() => this._onCreated());
        },

        _addMarker: function (e) {
            if (this.mode === null) {
                return;
            }

            const marker = L.Marker.routed(e.latlng.roundE8(), {
                riseOnHover: true,
                draggable: true,
                opacity: 0.5,
                color: (this.track.hasMarkers()) ? this.track.getLastMarker().getColorIndex() : this.track.getCurrentColor(),
                type: 'waypoint',
            });

            // Ignore this marker if same as previous
            if (this.track.hasMarkers() && this.track.getLastMarker().getLatLng().equals(marker.getLatLng()))
                return;

            marker.add(this.track);
        },
    });

})(jQuery);
<|endoftext|>


<commit_before>/*global define, amplify*/
define([
    'jquery',
    'require',
    'underscore',
    'loglevel',
    'fx-m-c/config/errors',
    'fx-m-c/config/events',
    'fx-m-c/config/config',
    'fx-m-c/config/config-default',
    'fenix-ui-map',
    'fx-common/pivotator/start',
    'fx-common/pivotator/fenixtool',
    'amplify'
], function ($, require, _, log, ERR, EVT,
    C, CD,
    FMMap,
    Pivotator,
    Fenixtool
    ) {

    'use strict';

    function MapCreator(o) {

        log.info("FENIX MapCreator");
        log.info(o);

        _.extend(this, CD, C, {initial: o});

        this._parseInput(o);

        this.fenix_ui_mapConfig = o.fenix_ui_map;

        var valid = this._validateInput();

        if (valid === true) {

            this._initVariables();

            this._bindEventListeners();

            this._renderMap();

            return this;

        } else {
            log.error("Impossible to create MapCreator");
            log.error(valid)
        }

    }

    // API

    /**
     * pub/sub
     * @return {Object} MapCreator instance
     */
    MapCreator.prototype.on = function (channel, fn) {
        if (!this.channels[channel]) {
            this.channels[channel] = [];
        }
        this.channels[channel].push({context: this, callback: fn});
        return this;
    };

    /**
     * Add a base layer to map
     */
    MapCreator.prototype.addBaseLayer = function (layer) {

        this.fenixMap.addTileLayer(layer, true);

        return this;
    };

    /**
     * Add a layer to map
     */
    MapCreator.prototype.addLayer = function (model) {

        var layer;

        //support simple Leaflet layer
        if(model instanceof L.TileLayer)
            return this.fenixMap.map.addLayer(model);

        // TODO: switch to check if it's a fenix layer
        if (!model.hasOwnProperty("metadata")) {
            this.errors.metadata = "Model does not contain 'metadata' attribute.";
            throw new Error("FENIX Map creator has not a valid configuration");
        }

        if (model.hasOwnProperty('data'))
            layer = this.createLayerFenixJoin(model);
        else
            layer = this.createLayerFenix(model);

        layer = new FM.layer(layer);

        this.fenixMap.addLayer(layer);

        return layer;
    };
    /**
     * Remove a layer from map
     */
    MapCreator.prototype.removeLayer = function (layer) {

        if (this.status.ready !== true) {
            return;
        }

        return this;
    };

    /**
     * Invalidate map size
     */
    MapCreator.prototype.invalidateSize = function () {

        if (this.status.ready !== true) {
            return;
        }

        this.fenixMap.invalidateSize()

        return this;
    };

    // end API

    MapCreator.prototype._parseInput = function () {

        this.id = this.initial.id;
        this.$el = $(this.initial.el);
        this.model = this.initial.model;

        //pivotator config
        var pc = {};

        pc.aggregationFn = this.initial.aggregationFn;

        pc.aggregations = this.initial.aggregations || [];
        pc.hidden = this.initial.hidden || [];
        pc.columns = this.initial.x;
        pc.values = this.initial.y || ["value"];
        pc.rows = this.initial.series;

        pc.formatter = this.initial.formatter || "value";
        pc.valueOutputType = this.initial.valueOutputType;
        pc.showRowHeaders = this.initial.showRowHeaders || false;
        pc.decimals = this.initial.decimals || 2;

        pc.showCode = this.initial.showCode || false;
        pc.showFlag = this.initial.showFlag || false;
        pc.showUnit = this.initial.showUnit || false;

        // add more pivotator config

        this.pivotatorConfig = pc;

    };

    MapCreator.prototype._validateInput = function () {

        var valid = true,
            errors = [];

        //set MapCreator id
        if (!this.id) {

            window.fx_map_id >= 0 ? window.fx_map_id++ : window.fx_map_id = 0;
            this.id = String(window.fx_map_id);
            log.warn("Impossible to find MapCreator id. Set auto id to: " + this.id);
        }

        //Check if $el exist
        if (this.$el.length === 0) {
            errors.push({code: ERR.MISSING_CONTAINER});
            log.warn("Impossible to find box container");
        }

        return errors.length > 0 ? errors : valid;

    };

    MapCreator.prototype._initVariables = function () {

        //pub/sub
        this.channels = {};

        this.status = {};
        this.status.ready = false;

        this.pivotator = new Pivotator();
        this.fenixTool = new Fenixtool();
    };

    MapCreator.prototype._bindEventListeners = function () {

        amplify.subscribe(this._getEventName(EVT.WINDOW_RESIZE), this, this.invalidateSize);
    };

    MapCreator.prototype._renderMap = function () {

        var self = this;

        //var myPivotatorConfig=this.fenixTool.parseInut(this.initial.model.metadata.dsd, this.pivotatorConfig);
        //var model = this.pivotator.pivot(this.model, this.pivotatorConfig);

        self.fenixMap = new FM.Map(self.$el, self.fenix_ui_mapConfig);
        self.fenixMap.createMap();

        self.addLayer(this.model)

         //TODO bind to Leaflet whenReady
        self.status.ready = true;  //To be set on map ready event

        setTimeout(_.bind(function () {
            self._trigger('ready');
        }, self), 0);
    };

    MapCreator.prototype._trigger = function (channel) {

        if (!this.channels[channel]) {
            return false;
        }
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = this.channels[channel].length; i < l; i++) {
            var subscription = this.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }

        return this;
    };

    MapCreator.prototype._getEventName = function (evt) {

        return this.id.concat(evt);

    };

    MapCreator.prototype._unbindEventListeners = function () {

        amplify.unsubscribe(this._getEventName(EVT.WINDOW_RESIZE), this.invalidateSize);
    };

    MapCreator.prototype.dispose = function () {

        //unbind event listeners
        this._unbindEventListeners();

        if (this.status.ready === true) {
            this.fenixMap.destroyMap();
        }

    };

    MapCreator.prototype.createLayerFenix = function (model, options) {
        var metadata = model.metadata;
        var layer = {};
        // Define the layer
        if (metadata.hasOwnProperty("dsd")) {
            layer.layers = "";
            if (metadata.dsd.hasOwnProperty("workspace"))
                layer.layers += metadata.dsd.workspace + ":";

            layer.layers += metadata.dsd.layerName;
        }
        else {
            this.errors['dsd'] = "Model['metadata'] does not contain 'dsd' attribute.";
            throw new Error("FENIX Map creator has not a valid configuration");
        }

        //TODO
        //if (model.hasOwnProperty("datasource"))
        //  layer.urlWMS = metadata["datasource"];

        layer.urlWMS = this.fenix_ui_map.DEFAULT_WMS_SERVER;
        layer.layertitle = {
            en: 'Data Layer'
        };
        layer.opacity = '0.9';
        return layer;
    };

    // JOIN
    MapCreator.prototype.createLayerFenixJoin = function (model) {
        if (this._validateJoinInput(model) === true) {

            // create the join layer
            var layer = this.getJoinLayer(model);

            _.extend(layer, this.join.style);

            var defPopupBuilder = "
{{"+ layer.joincolumnlabel +"}}"+
                "
{{{"+ layer.joincolumn + "}}} "+
                "{{measurementunit}}"+
                "

";

            // Layer title TODO: Add title if exist (check in the validator)
            if (model['metadata'].hasOwnProperty("title")) {
                if (model['metadata']['title'][this.lang] !== null) {
                    layer.layertitle = model['metadata']['title'][this.lang];
                }
            }
            else {
                layer.layertitle = model['metadata']['uid'];
            }

            // getting a title from the options
            if ( this.hasOwnProperty('layer') && this.layer.hasOwnProperty('layertitle')) {
                layer.layertitle = this.layer.layertitle;
            }

            if ( this.hasOwnProperty('layer') && this.layer.hasOwnProperty('popupBuilder')) {
                layer.popupBuilder = this.layer.popupBuilder;
            }

            layer.customgfi = {
                showpopup: true,
                content: {
                    EN: _.isFunction(layer.popupBuilder) ? layer.popupBuilder(layer.joincolumnlabel, layer.joincolumn) : defPopupBuilder
                }
            };

            // TODO: add check on the zoomto data (move it to a function)
            var codes = []
            layer.joindata.forEach(function (code) {
                _.keys(code).forEach(function (key) {
                    codes.push(key);
                });
            });
            var zoomlayer = layer.layers.split(":");
            zoomlayer = zoomlayer.length > 1? zoomlayer[1]: zoomlayer[0];
            this.fenixMap.zoomTo(zoomlayer, layer.joincolumn, codes);
            return layer;
        } else {
            console.error(this.errors);
            throw new Error("FENIX Map creator has not a valid JOIN configuration");
        }
    };

    MapCreator.prototype.getJoinLayer = function (model) {

        var metadata = model['metadata'],
            geoColumn = {},
            valueColumn = {},
            muColumn = {};

        metadata['dsd']['columns'].forEach(_.bind(function (col, index) {
            if (col.subject === this.geoSubject || col.id === this.geoSubject ) {
                geoColumn = col;
                geoColumn.index = index;
            }
            if (col.subject === this.valueSubject || col.id === this.valueSubject ) {
                valueColumn = col;
                valueColumn.index = index;
            }
            if (col.subject === this.muSubject) {
                muColumn = col;
                muColumn.index = index;
            }
        }, this));

        // getting the right measurement unit if the new label exists
        metadata['dsd']['columns'].forEach(_.bind(function (column, index) {
            if (muColumn.id + '_' + this.lang) {
                muColumn = column;
                muColumn.index = index;
            }
        }, this));

        if (this._validateJoinColumnInput(geoColumn)) {

            var layer = null;
            var codelist = geoColumn['domain']['codes'][0]['idCodeList'].toLowerCase();

            if (this.join.layerMapping[codelist]) {
                layer = this.join.layerMapping[codelist];
            }
            else {
                geoColumn['domain']['codes'][0].idCodeList.toLowerCase();
                // TODO: Handle reference Area
                var referenceArea = metadata["meContent"]["seReferencePopulation"]["referenceArea"]['codes'][0].code.toLowerCase();
                layer = this.join.layerMapping[codelist + "_" + referenceArea];
            }

            // check measurementunit
            // TODO: Add measurement unit to the layer definition (using label column of the mu)
            //

            // get joinData
            layer.joindata = this.getJoinData(model['data'], geoColumn.index, valueColumn.index);

            // TODO: check on the column index
            layer.measurementunit = model['data'][0][muColumn.index];

            // TODO: check if is the right legendtitle
            layer.legendtitle = layer.measurementunit;

            return layer;

        } else{
            console.error('Error JoinColumnInput not valid')
        }
    };

    MapCreator.prototype.getJoinData = function (data, geoColumnIndex, valueColumnIhdex) {
        var joindata = [];

        // TODO: remove cachedValues on final version. Check on join data consistency?
        var cachedValues = {}
        // TODO: add on check
        data.forEach(_.bind(function (row) {
            var obj = {}
            var code = row[geoColumnIndex];
            var value = row[valueColumnIhdex];
            if (code && value) {
                obj[code] = value ;
                if (!cachedValues.hasOwnProperty(code)) {
                    // check null values
                    cachedValues[code] = true;
                    joindata.push(obj);
                }
            }
        }, this));

        return joindata;
    };

    MapCreator.prototype._validateJoinInput = function (model) {
        this.errors = {};

        //Metadata TODO: add all metadata checks
        if (!model.hasOwnProperty("metadata")) {
            this.errors.metadata = "'metadata' attribute not present.";
        }

        //Data
        if (!model.hasOwnProperty("data")) {
            this.errors.data = "'data' attribute not present.";
        }

        return (_.keys(this.errors).length === 0);
    };

    MapCreator.prototype._validateJoinColumnInput = function (column) {
        this.errors = {};

        //Metadata TODO: add all metadata checks
        if (!column.hasOwnProperty('key')) {
            this.errors.column = "'key' attribute not present.";
        }
        else {
            if (column.key !== true) {
                this.errors.column = "'key' is not true.";
            }
        }

        if (!column.hasOwnProperty('dataType')) {
            this.errors.column = "'dataType' attribute not present.";
        }
        else {
            if (column.dataType !== 'code') {
                this.errors.column = "'dataType' attribute is not a coding system.";
            }
        }

        // TODO: check domain and referencearea if needed

        return (Object.keys(this.errors).length === 0);
    };

    return MapCreator;
});<commit_msg>popup<commit_after>/*global define, amplify*/
define([
    'jquery',
    'require',
    'underscore',
    'loglevel',
    'fx-m-c/config/errors',
    'fx-m-c/config/events',
    'fx-m-c/config/config',
    'fx-m-c/config/config-default',
    'fenix-ui-map',
    'fx-common/pivotator/start',
    'fx-common/pivotator/fenixtool',
    'amplify'
], function ($, require, _, log, ERR, EVT,
    C, CD,
    FMMap,
    Pivotator,
    Fenixtool
    ) {

    'use strict';

    function MapCreator(o) {

        log.info("FENIX MapCreator");
        log.info(o);

        _.extend(this, CD, C, {initial: o});

        this._parseInput(o);

        this.fenix_ui_mapConfig = o.fenix_ui_map;

        var valid = this._validateInput();

        if (valid === true) {

            this._initVariables();

            this._bindEventListeners();

            this._renderMap();

            return this;

        } else {
            log.error("Impossible to create MapCreator");
            log.error(valid)
        }

    }

    // API

    /**
     * pub/sub
     * @return {Object} MapCreator instance
     */
    MapCreator.prototype.on = function (channel, fn) {
        if (!this.channels[channel]) {
            this.channels[channel] = [];
        }
        this.channels[channel].push({context: this, callback: fn});
        return this;
    };

    /**
     * Add a base layer to map
     */
    MapCreator.prototype.addBaseLayer = function (layer) {

        this.fenixMap.addTileLayer(layer, true);

        return this;
    };

    /**
     * Add a layer to map
     */
    MapCreator.prototype.addLayer = function (model) {

        var layer;

        //support simple Leaflet layer
        if(model instanceof L.TileLayer)
            return this.fenixMap.map.addLayer(model);

        // TODO: switch to check if it's a fenix layer
        if (!model.hasOwnProperty("metadata")) {
            this.errors.metadata = "Model does not contain 'metadata' attribute.";
            throw new Error("FENIX Map creator has not a valid configuration");
        }

        if (model.hasOwnProperty('data'))
            layer = this.createLayerFenixJoin(model);
        else
            layer = this.createLayerFenix(model);

        layer = new FM.layer(layer);

        this.fenixMap.addLayer(layer);

        return layer;
    };
    /**
     * Remove a layer from map
     */
    MapCreator.prototype.removeLayer = function (layer) {

        if (this.status.ready !== true) {
            return;
        }

        return this;
    };

    /**
     * Invalidate map size
     */
    MapCreator.prototype.invalidateSize = function () {

        if (this.status.ready !== true) {
            return;
        }

        this.fenixMap.invalidateSize()

        return this;
    };

    // end API

    MapCreator.prototype._parseInput = function () {

        this.id = this.initial.id;
        this.$el = $(this.initial.el);
        this.model = this.initial.model;

        //pivotator config
        var pc = {};

        pc.aggregationFn = this.initial.aggregationFn;

        pc.aggregations = this.initial.aggregations || [];
        pc.hidden = this.initial.hidden || [];
        pc.columns = this.initial.x;
        pc.values = this.initial.y || ["value"];
        pc.rows = this.initial.series;

        pc.formatter = this.initial.formatter || "value";
        pc.valueOutputType = this.initial.valueOutputType;
        pc.showRowHeaders = this.initial.showRowHeaders || false;
        pc.decimals = this.initial.decimals || 2;

        pc.showCode = this.initial.showCode || false;
        pc.showFlag = this.initial.showFlag || false;
        pc.showUnit = this.initial.showUnit || false;

        // add more pivotator config

        this.pivotatorConfig = pc;

    };

    MapCreator.prototype._validateInput = function () {

        var valid = true,
            errors = [];

        //set MapCreator id
        if (!this.id) {

            window.fx_map_id >= 0 ? window.fx_map_id++ : window.fx_map_id = 0;
            this.id = String(window.fx_map_id);
            log.warn("Impossible to find MapCreator id. Set auto id to: " + this.id);
        }

        //Check if $el exist
        if (this.$el.length === 0) {
            errors.push({code: ERR.MISSING_CONTAINER});
            log.warn("Impossible to find box container");
        }

        return errors.length > 0 ? errors : valid;

    };

    MapCreator.prototype._initVariables = function () {

        //pub/sub
        this.channels = {};

        this.status = {};
        this.status.ready = false;

        this.pivotator = new Pivotator();
        this.fenixTool = new Fenixtool();
    };

    MapCreator.prototype._bindEventListeners = function () {

        amplify.subscribe(this._getEventName(EVT.WINDOW_RESIZE), this, this.invalidateSize);
    };

    MapCreator.prototype._renderMap = function () {

        var self = this;

        //var myPivotatorConfig=this.fenixTool.parseInut(this.initial.model.metadata.dsd, this.pivotatorConfig);
        //var model = this.pivotator.pivot(this.model, this.pivotatorConfig);

        self.fenixMap = new FM.Map(self.$el, self.fenix_ui_mapConfig);
        self.fenixMap.createMap();

        self.addLayer(this.model)

         //TODO bind to Leaflet whenReady
        self.status.ready = true;  //To be set on map ready event

        setTimeout(_.bind(function () {
            self._trigger('ready');
        }, self), 0);
    };

    MapCreator.prototype._trigger = function (channel) {

        if (!this.channels[channel]) {
            return false;
        }
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = this.channels[channel].length; i < l; i++) {
            var subscription = this.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }

        return this;
    };

    MapCreator.prototype._getEventName = function (evt) {

        return this.id.concat(evt);

    };

    MapCreator.prototype._unbindEventListeners = function () {

        amplify.unsubscribe(this._getEventName(EVT.WINDOW_RESIZE), this.invalidateSize);
    };

    MapCreator.prototype.dispose = function () {

        //unbind event listeners
        this._unbindEventListeners();

        if (this.status.ready === true) {
            this.fenixMap.destroyMap();
        }

    };

    MapCreator.prototype.createLayerFenix = function (model, options) {
        var metadata = model.metadata;
        var layer = {};
        // Define the layer
        if (metadata.hasOwnProperty("dsd")) {
            layer.layers = "";
            if (metadata.dsd.hasOwnProperty("workspace"))
                layer.layers += metadata.dsd.workspace + ":";

            layer.layers += metadata.dsd.layerName;
        }
        else {
            this.errors['dsd'] = "Model['metadata'] does not contain 'dsd' attribute.";
            throw new Error("FENIX Map creator has not a valid configuration");
        }

        //TODO
        //if (model.hasOwnProperty("datasource"))
        //  layer.urlWMS = metadata["datasource"];

        layer.urlWMS = this.fenix_ui_map.DEFAULT_WMS_SERVER;
        layer.layertitle = {
            en: 'Data Layer'
        };
        layer.opacity = '0.9';
        return layer;
    };

    // JOIN
    MapCreator.prototype.createLayerFenixJoin = function (model) {
        if (this._validateJoinInput(model) === true) {

            // create the join layer
            var layer = this.getJoinLayer(model);

            _.extend(layer, this.join.style);

            var defPopup = "
{{"+ layer.joincolumnlabel +"}}"+
                "
{{{"+ layer.joincolumn + "}}} "+
                "{{measurementunit}}"+
                "

";

            // Layer title TODO: Add title if exist (check in the validator)
            if (model['metadata'].hasOwnProperty("title")) {
                if (model['metadata']['title'][this.lang]) {
                    layer.layertitle = model['metadata']['title'][this.lang];
                    console.log('title',this.lang, layer.layertitle)
                }
            }
            else {
                layer.layertitle = model['metadata']['uid'];
            }

            // getting a title from the options
            if ( this.hasOwnProperty('layer') && this.layer.hasOwnProperty('layertitle')) {
                layer.layertitle = this.layer.layertitle;
            }

            if ( this.hasOwnProperty('layer') && this.layer.hasOwnProperty('popupBuilder')) {
                layer.popupBuilder = this.layer.popupBuilder;
            }

            layer.customgfi = {
                showpopup: true,
                content: {
                    EN: _.isFunction(layer.popupBuilder) ? layer.popupBuilder(layer.joincolumnlabel, layer.joincolumn) : defPopup
                }
            };

            // TODO: add check on the zoomto data (move it to a function)
            var codes = [];
            layer.joindata.forEach(function (code) {
                _.keys(code).forEach(function (key) {
                    codes.push(key);
                });
            });

            var zoomlayer = layer.layers.split(":");

            zoomlayer = zoomlayer.length > 1? zoomlayer[1]: zoomlayer[0];

            this.fenixMap.zoomTo(zoomlayer, layer.joincolumn, codes);

            return layer;
        } else {
            console.error(this.errors);
            throw new Error("FENIX Map creator has not a valid JOIN configuration");
        }
    };

    MapCreator.prototype.getJoinLayer = function (model) {

        var metadata = model['metadata'],
            geoColumn = {},
            valueColumn = {},
            muColumn = {};

        metadata['dsd']['columns'].forEach(_.bind(function (col, index) {
            if (col.subject === this.geoSubject || col.id === this.geoSubject ) {
                geoColumn = col;
                geoColumn.index = index;
            }
            if (col.subject === this.valueSubject || col.id === this.valueSubject ) {
                valueColumn = col;
                valueColumn.index = index;
            }
            if (col.subject === this.muSubject) {
                muColumn = col;
                muColumn.index = index;
            }
        }, this));

        // getting the right measurement unit if the new label exists
        metadata['dsd']['columns'].forEach(_.bind(function (column, index) {
            if (muColumn.id + '_' + this.lang) {
                muColumn = column;
                muColumn.index = index;
            }
        }, this));

        if (this._validateJoinColumnInput(geoColumn)) {

            var layer = null;
            var codelist = geoColumn['domain']['codes'][0]['idCodeList'].toLowerCase();

            if (this.join.layerMapping[codelist]) {
                layer = this.join.layerMapping[codelist];
            }
            else {
                geoColumn['domain']['codes'][0].idCodeList.toLowerCase();
                // TODO: Handle reference Area
                var referenceArea = metadata["meContent"]["seReferencePopulation"]["referenceArea"]['codes'][0].code.toLowerCase();
                layer = this.join.layerMapping[codelist + "_" + referenceArea];
            }

            // check measurementunit
            // TODO: Add measurement unit to the layer definition (using label column of the mu)
            //

            // get joinData
            layer.joindata = this.getJoinData(model['data'], geoColumn.index, valueColumn.index);

            // TODO: check on the column index
            layer.measurementunit = model['data'][0][muColumn.index];

            // TODO: check if is the right legendtitle
            layer.legendtitle = layer.measurementunit;

            return layer;

        } else{
            console.error('Error JoinColumnInput not valid')
        }
    };

    MapCreator.prototype.getJoinData = function (data, geoColumnIndex, valueColumnIhdex) {
        var joindata = [];

        // TODO: remove cachedValues on final version. Check on join data consistency?
        var cachedValues = {}
        // TODO: add on check
        data.forEach(_.bind(function (row) {
            var obj = {}
            var code = row[geoColumnIndex];
            var value = row[valueColumnIhdex];
            if (code && value) {
                obj[code] = value ;
                if (!cachedValues.hasOwnProperty(code)) {
                    // check null values
                    cachedValues[code] = true;
                    joindata.push(obj);
                }
            }
        }, this));

        return joindata;
    };

    MapCreator.prototype._validateJoinInput = function (model) {
        this.errors = {};

        //Metadata TODO: add all metadata checks
        if (!model.hasOwnProperty("metadata")) {
            this.errors.metadata = "'metadata' attribute not present.";
        }

        //Data
        if (!model.hasOwnProperty("data")) {
            this.errors.data = "'data' attribute not present.";
        }

        return (_.keys(this.errors).length === 0);
    };

    MapCreator.prototype._validateJoinColumnInput = function (column) {
        this.errors = {};

        //Metadata TODO: add all metadata checks
        if (!column.hasOwnProperty('key')) {
            this.errors.column = "'key' attribute not present.";
        }
        else {
            if (column.key !== true) {
                this.errors.column = "'key' is not true.";
            }
        }

        if (!column.hasOwnProperty('dataType')) {
            this.errors.column = "'dataType' attribute not present.";
        }
        else {
            if (column.dataType !== 'code') {
                this.errors.column = "'dataType' attribute is not a coding system.";
            }
        }

        // TODO: check domain and referencearea if needed

        return (Object.keys(this.errors).length === 0);
    };

    return MapCreator;
});<|endoftext|>


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

const file = require('gulp-file');
const fs = require('fs-extra');
const gulp = require('gulp');
const gulpWatch = require('gulp-watch');
const {
  endBuildStep,
  mkdirSync,
  printNobuildHelp,
  toPromise,
} = require('./helpers');
const {buildExtensions, extensions} = require('./extension-helpers');
const {jsifyCssAsync} = require('./jsify-css');
const {maybeUpdatePackages} = require('./update-packages');

/**
 * Entry point for 'gulp css'
 * @return {!Promise}
 */
async function css() {
  maybeUpdatePackages();
  printNobuildHelp();
  return compileCss();
}

const cssEntryPoints = [
  {
    path: 'ampdoc.css',
    outJs: 'ampdoc.css.js',
    outCss: 'v0.css',
  },
  {
    path: 'ampshared.css',
    outJs: 'ampshared.css.js',
    outCss: 'v0.css',
    append: true,
  },
  {
    path: 'video-autoplay.css',
    outJs: 'video-autoplay.css.js',
    outCss: 'video-autoplay.css',
  },
];

/**
 * Compile all the css and drop in the build folder
 * @param {boolean} watch
 * @param {boolean=} opt_compileAll
 * @return {!Promise}
 */
function compileCss(watch, opt_compileAll) {
  if (watch) {
    gulpWatch('css/**/*.css', function() {
      compileCss();
    });
  }

  /**
   * Writes CSS to build folder
   *
   * @param {string} css
   * @param {string} jsFilename
   * @param {string} cssFilename
   * @param {boolean} append append CSS to existing file
   * @return {Promise}
   */
  function writeCss(css, jsFilename, cssFilename, append) {
    return toPromise(
      // cssText is hardcoded in AmpCodingConvention.java
      file(jsFilename, 'export const cssText = ' + JSON.stringify(css), {
        src: true,
      })
        .pipe(gulp.dest('build'))
        .on('end', function() {
          mkdirSync('build');
          mkdirSync('build/css');
          if (append) {
            fs.appendFileSync(`build/css/${cssFilename}`, css);
          } else {
            fs.writeFileSync(`build/css/${cssFilename}`, css);
          }
        })
    );
  }

  /**
   * @param {string} path
   * @param {string} outJs
   * @param {string} outCss
   * @param {boolean} append
   */
  function writeCssEntryPoint(path, outJs, outCss, append) {
    return jsifyCssAsync(`css/${path}`).then(css =>
      writeCss(css, outJs, outCss, append)
    );
  }

  const startTime = Date.now();

  // Used by `gulp unit --local_changes` to map CSS files to JS files.
  fs.writeFileSync('EXTENSIONS_CSS_MAP', JSON.stringify(extensions));

  let promise = Promise.resolve();

  cssEntryPoints.forEach(entryPoint => {
    const {path, outJs, outCss, append} = entryPoint;
    promise = promise.then(() =>
      writeCssEntryPoint(path, outJs, outCss, append)
    );
  });

  return promise
    .then(() =>
      buildExtensions({
        compileOnlyCss: true,
        compileAll: opt_compileAll,
      })
    )
    .then(() => {
      endBuildStep('Recompiled all CSS files into', 'build/', startTime);
    });
}

module.exports = {
  css,
  compileCss,
  cssEntryPoints,
};

css.description = 'Recompile css to build directory';
<commit_msg>Use css.js for autoplay styles (#22713)<commit_after>/**
 * Copyright 2019 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const file = require('gulp-file');
const fs = require('fs-extra');
const gulp = require('gulp');
const gulpWatch = require('gulp-watch');
const {
  endBuildStep,
  mkdirSync,
  printNobuildHelp,
  toPromise,
} = require('./helpers');
const {buildExtensions, extensions} = require('./extension-helpers');
const {jsifyCssAsync} = require('./jsify-css');
const {maybeUpdatePackages} = require('./update-packages');

/**
 * Entry point for 'gulp css'
 * @return {!Promise}
 */
async function css() {
  maybeUpdatePackages();
  printNobuildHelp();
  return compileCss();
}

const cssEntryPoints = [
  {
    path: 'ampdoc.css',
    outJs: 'ampdoc.css.js',
    outCss: 'v0.css',
  },
  {
    path: 'ampshared.css',
    outJs: 'ampshared.css.js',
    outCss: 'v0.css',
    append: true,
  },
  {
    path: 'video-autoplay.css',
    outJs: 'video-autoplay.css.js',
    outCss: 'video-autoplay-out.css',
  },
];

/**
 * Compile all the css and drop in the build folder
 * @param {boolean} watch
 * @param {boolean=} opt_compileAll
 * @return {!Promise}
 */
function compileCss(watch, opt_compileAll) {
  if (watch) {
    gulpWatch('css/**/*.css', function() {
      compileCss();
    });
  }

  /**
   * Writes CSS to build folder
   *
   * @param {string} css
   * @param {string} jsFilename
   * @param {string} cssFilename
   * @param {boolean} append append CSS to existing file
   * @return {Promise}
   */
  function writeCss(css, jsFilename, cssFilename, append) {
    return toPromise(
      // cssText is hardcoded in AmpCodingConvention.java
      file(jsFilename, 'export const cssText = ' + JSON.stringify(css), {
        src: true,
      })
        .pipe(gulp.dest('build'))
        .on('end', function() {
          mkdirSync('build');
          mkdirSync('build/css');
          if (append) {
            fs.appendFileSync(`build/css/${cssFilename}`, css);
          } else {
            fs.writeFileSync(`build/css/${cssFilename}`, css);
          }
        })
    );
  }

  /**
   * @param {string} path
   * @param {string} outJs
   * @param {string} outCss
   * @param {boolean} append
   */
  function writeCssEntryPoint(path, outJs, outCss, append) {
    return jsifyCssAsync(`css/${path}`).then(css =>
      writeCss(css, outJs, outCss, append)
    );
  }

  const startTime = Date.now();

  // Used by `gulp unit --local_changes` to map CSS files to JS files.
  fs.writeFileSync('EXTENSIONS_CSS_MAP', JSON.stringify(extensions));

  let promise = Promise.resolve();

  cssEntryPoints.forEach(entryPoint => {
    const {path, outJs, outCss, append} = entryPoint;
    promise = promise.then(() =>
      writeCssEntryPoint(path, outJs, outCss, append)
    );
  });

  return promise
    .then(() =>
      buildExtensions({
        compileOnlyCss: true,
        compileAll: opt_compileAll,
      })
    )
    .then(() => {
      endBuildStep('Recompiled all CSS files into', 'build/', startTime);
    });
}

module.exports = {
  css,
  compileCss,
  cssEntryPoints,
};

css.description = 'Recompile css to build directory';
<|endoftext|>


<commit_before>module.exports = () => {
  return `module.exports = {
  hotkey: 'alt+space',
  theme: 'tinytacoteam/zazu-light-theme',
  plugins: [
    'tinytacoteam/zazu-calculator',
    'tinytacoteam/zazu-file-finder',
    'tinytacoteam/zazu-calculator',
    'tinytacoteam/zazu-system',
    {
      name: 'tinytacoteam/zazu-clipboard',
      variables: {
        ClipboardKey: 'cmd+shift+v',
      },
    },
  ],
}`
}
<commit_msg>Replace duplicate calculator with fallback.<commit_after>module.exports = () => {
  return `module.exports = {
  hotkey: 'alt+space',
  theme: 'tinytacoteam/zazu-light-theme',
  plugins: [
    'tinytacoteam/zazu-calculator',
    'tinytacoteam/zazu-file-finder',
    'tinytacoteam/zazu-fallback',
    'tinytacoteam/zazu-system',
    {
      name: 'tinytacoteam/zazu-clipboard',
      variables: {
        ClipboardKey: 'cmd+shift+v',
      },
    },
  ],
}`
}
<|endoftext|>


<commit_before>
require('html-element')
var h = require('hyperscript')

function a (href, linked, classes) {
  return h('a'+(classes || ''), {href: href}, linked)
}

function img (src, width, classes) {
  return h('img'+(classes || ''), {src: src, width: width ? width+'px' : null})
}

//okay, so funders object should also have data about the iteration.
//that is the wallet, the tasks, etc.
//we want to show the state at the top (fundedness, started, finished)
//which can also be represented within the progress bar.
//given that, it will be easy to generate the detailed progress bar.

module.exports = function (iteration) {
  var total = 0
  var funders = iteration.funders
  return h('div',
    h('h1',
      a(
        iteration.html_url,
        h('span', iteration.title)
      )
    ),

    h('h3', 'Funding', iteration.state.funded ? ' (Funded)' : ''),
    h('div', img(iteration.badge_url, null, '.badge')),
    a('bitcoin:' + iteration.wallet, 'bitcoin: '+ iteration.wallet),

    h('h3', 'Tasks', iteration.state.complete ? '(Complete)' : ''),
    h('div',
      h('ul',
        iteration.links.map(function (l) {
          var url = l.url.replace('https://api.github.com/repos', 'https://github.com')
          return h('li', a(url, url), l.closed ? h('bold',' (DONE)') : null)
        })
      )
    ),

    h('h3', 'Funders',
        ' ('+iteration.sum.toPrecision(4) +'btc, '
        + (100 * ~~(iteration.sum/iteration.target))
        + '% funded)'
    ),
    h('div.row',
      funders.map(function (funder) {
        total += funder.sum

        /*
        +----+
        |THMB| USERNAME   0.123
        |    |
        +----+ trx,...
        */

        return h('div.ib',
          h('div.funder',
            a(funder.html_url, img(funder.avatar_url || '/grumpy.jpg', 80), '.avatar'),
            h('div.uline.fit',
              h('span.pad10',
                h('span.i2', a(funder.html_url, funder.login)),
                h('span', funder.sum/1e8)
              )
            ),
            h('div.v1.pad10',
              funder.txs.map(function (e) {
                return h('div.sm', a('https://blockchain.info/tx/' + e.hash, e.hash.substring(0, 45)+'...'))
              })
            )
          )
        )
      })
    )
  )
}
<commit_msg>show progress<commit_after>
require('html-element')
var h = require('hyperscript')

function a (href, linked, classes) {
  return h('a'+(classes || ''), {href: href}, linked)
}

function img (src, width, classes) {
  return h('img'+(classes || ''), {src: src, width: width ? width+'px' : null})
}

//okay, so funders object should also have data about the iteration.
//that is the wallet, the tasks, etc.
//we want to show the state at the top (fundedness, started, finished)
//which can also be represented within the progress bar.
//given that, it will be easy to generate the detailed progress bar.

module.exports = function (iteration) {
  var total = 0
  var funders = iteration.funders
  return h('div',
    h('h1',
      a(
        iteration.html_url,
        h('span', iteration.title)
      )
    ),

    h('h3', 'Funding', iteration.state.funded ? ' (Funded)' : ''),
    h('div', img(iteration.badge_url, null, '.badge')),
    a('bitcoin:' + iteration.wallet, 'bitcoin: '+ iteration.wallet),

    h('h3', 'Tasks', iteration.state.complete ? '(Complete)' : ''),
    h('div',
      h('ul',
        iteration.links.map(function (l) {
          var url = l.url.replace('https://api.github.com/repos', 'https://github.com')
          return h('li', a(url, url), l.closed ? h('bold',' (DONE)') : null)
        })
      )
    ),

    h('h3', 'Funders',
        ' ('+iteration.sum.toPrecision(4) +'btc, '
        + (100 * iteration.progress)
        + '% funded)'
    ),
    h('div.row',
      funders.map(function (funder) {
        total += funder.sum

        /*
        +----+
        |THMB| USERNAME   0.123
        |    |
        +----+ trx,...
        */

        return h('div.ib',
          h('div.funder',
            a(funder.html_url, img(funder.avatar_url || '/grumpy.jpg', 80), '.avatar'),
            h('div.uline.fit',
              h('span.pad10',
                h('span.i2', a(funder.html_url, funder.login)),
                h('span', funder.sum/1e8)
              )
            ),
            h('div.v1.pad10',
              funder.txs.map(function (e) {
                return h('div.sm', a('https://blockchain.info/tx/' + e.hash, e.hash.substring(0, 45)+'...'))
              })
            )
          )
        )
      })
    )
  )
}
<|endoftext|>


<commit_before>"use strict"

const { execSync } = require ('child_process')
const log          = require ('ololog').noLocate
const { groupBy }  = require ('ccxt')
const { values }   = Object
const assert       = require ('assert')

const tags = execSync ('git tag').toString ().split ('\n').filter (s => s).map (t => {

    const [major, minor, patch] = t.split ('.').map (Number)

    assert (major < 100)
    assert (minor < 100)

    return {
        key: (major * 100) + minor,
        tag: t,
        major, minor, patch
    }
})

const tagsByMajor = values (groupBy (tags, 'key')).sort ((a, b) => a[0].key - b[0].key)

// Preserve all tags for first 3 minor versions

for (let i = 0; i < 3; i++) {

    const tags = tagsByMajor.pop ()

    log.green.bright ('Preserving', tags[0].tag, '...', tags[tags.length - 1].tag)
}

// For older versions, leave only "round" numbered versions (1/10th)

let tagsToDelete = []

for (const tags of tagsByMajor) {

    for (const { tag, patch } of tags) {

        if (String (patch).replace (/([^0]+)0+/, '$1').length == 1) {
            log.green ('Preserving', tag)

        } else {
            tagsToDelete.push (tag)
        }
    }
}

log.bright.red ('Deleting', tagsToDelete.length, 'tags...')

// TODO<commit_msg>require path in build/cleanup-old-tags.js [ci skip]<commit_after>"use strict"

const { execSync } = require ('child_process')
const log          = require ('ololog').noLocate
const { groupBy }  = require ('../ccxt.js')
const { values }   = Object
const assert       = require ('assert')

const tags = execSync ('git tag').toString ().split ('\n').filter (s => s).map (t => {

    const [major, minor, patch] = t.split ('.').map (Number)

    assert (major < 100)
    assert (minor < 100)

    return {
        key: (major * 100) + minor,
        tag: t,
        major, minor, patch
    }
})

const tagsByMajor = values (groupBy (tags, 'key')).sort ((a, b) => a[0].key - b[0].key)

// Preserve all tags for first 3 minor versions

for (let i = 0; i < 3; i++) {

    const tags = tagsByMajor.pop ()

    log.green.bright ('Preserving', tags[0].tag, '...', tags[tags.length - 1].tag)
}

// For older versions, leave only "round" numbered versions (1/10th)

let tagsToDelete = []

for (const tags of tagsByMajor) {

    for (const { tag, patch } of tags) {

        if (String (patch).replace (/([^0]+)0+/, '$1').length == 1) {
            log.green ('Preserving', tag)

        } else {
            tagsToDelete.push (tag)
        }
    }
}

log.bright.red ('Deleting', tagsToDelete.length, 'tags...')

// TODO
<|endoftext|>


<commit_before>/*
**  ComponentJS -- Component System for JavaScript <http://componentjs.com>
**  Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License, v. 2.0. If a copy of the MPL was not distributed with this
**  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

ace.define('ace/mode/cjsc', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/javascript_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/range', 'ace/worker/worker_client', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle'], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var Tokenizer = require("../tokenizer").Tokenizer;
var cjscHighlightRules = require("./cjsc_highlight_rules").cjscHighlightRules;

var Mode = function() {
    var highlighter = new cjscHighlightRules();
    this.$tokenizer = new Tokenizer(highlighter.getRules());
};
oop.inherits(Mode, TextMode);

(function() {
    this.lineCommentStart = "//";
    this.blockComment = {start: "/*", end: "*/"};
    // Extra logic goes here.
}).call(Mode.prototype);

exports.Mode = Mode;
});

ace.define('ace/mode/cjsc_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/javascript_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/range', 'ace/worker/worker_client', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle'], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;

var cjscHighlightRules = function() {
    // regexp must not have capturing parentheses. Use (?:) instead.
    // regexps are ordered -> the first match is used

    this.$rules = { start:
       [{
            token: 'keyword',
            regex: 'temporal-constraint|peephole-constraint|rationale'
        }, {
            token: 'keyword',
            regex: 'condition|sequence|filter|link'
        }, {
            token: 'keyword',
            regex: 'result'
        }, {
            token: 'keyword.operator',
            regex: '==|!=|<=|>=|<|>|=~|!~|!|&&|\\|\\|'
        }, {
            token: 'function',
            regex: 'isParent|distance|contains'
        }, {
            token: 'constant',
            regex: 'sourceType|originType|operation|origin|source|parameters'
        }, {
            token: 'constant',
            regex: 'last|first|PASS_FINAL|FAIL_FINAL|PASS|FAIL'
        }, {
            token: 'string',
            regex: /'(.*)'/
        }
       ]
    }
    this.normalizeRules();
};

oop.inherits(cjscHighlightRules, TextHighlightRules);

exports.cjscHighlightRules = cjscHighlightRules;
});<commit_msg>Added helper functions to keywords for highlighting<commit_after>/*
**  ComponentJS -- Component System for JavaScript <http://componentjs.com>
**  Copyright (c) 2009-2013 Ralf S. Engelschall <http://engelschall.com>
**
**  This Source Code Form is subject to the terms of the Mozilla Public
**  License, v. 2.0. If a copy of the MPL was not distributed with this
**  file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

ace.define('ace/mode/cjsc', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/javascript_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/range', 'ace/worker/worker_client', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle'], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var Tokenizer = require("../tokenizer").Tokenizer;
var cjscHighlightRules = require("./cjsc_highlight_rules").cjscHighlightRules;

var Mode = function() {
    var highlighter = new cjscHighlightRules();
    this.$tokenizer = new Tokenizer(highlighter.getRules());
};
oop.inherits(Mode, TextMode);

(function() {
    this.lineCommentStart = "//";
    this.blockComment = {start: "/*", end: "*/"};
    // Extra logic goes here.
}).call(Mode.prototype);

exports.Mode = Mode;
});

ace.define('ace/mode/cjsc_highlight_rules', ['require', 'exports', 'module' , 'ace/lib/oop', 'ace/mode/text', 'ace/tokenizer', 'ace/mode/javascript_highlight_rules', 'ace/mode/matching_brace_outdent', 'ace/range', 'ace/worker/worker_client', 'ace/mode/behaviour/cstyle', 'ace/mode/folding/cstyle'], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;

var cjscHighlightRules = function() {
    // regexp must not have capturing parentheses. Use (?:) instead.
    // regexps are ordered -> the first match is used

    this.$rules = { start:
       [{
            token: 'keyword',
            regex: 'temporal-constraint|peephole-constraint|rationale'
        }, {
            token: 'keyword',
            regex: 'condition|sequence|filter|link|isParent|distance|contains|state'
        }, {
            token: 'keyword',
            regex: 'result'
        }, {
            token: 'keyword.operator',
            regex: '==|!=|<=|>=|<|>|=~|!~|!|&&|\\|\\|'
        }, {
            token: 'constant',
            regex: 'sourceType|originType|operation|origin|source|parameters'
        }, {
            token: 'constant',
            regex: 'last|first|PASS_FINAL|FAIL_FINAL|PASS|FAIL'
        }, {
            token: 'string',
            regex: /'(.*)'/
        }
       ]
    }
    this.normalizeRules();
};

oop.inherits(cjscHighlightRules, TextHighlightRules);

exports.cjscHighlightRules = cjscHighlightRules;
});<|endoftext|>