diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index e4d9eed4..8e4c928f 100644 --- a/umap/static/umap/base.css +++ b/umap/static/umap/base.css @@ -508,6 +508,7 @@ i.info { .umap-layer-properties-container, .umap-footer-container, .umap-browse-data, +.umap-filter-data, .umap-browse-datalayers { padding: 0 10px; } diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index b7b9bff7..2ca538b9 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -434,6 +434,43 @@ L.U.MoreControls = L.Control.extend({ }); +L.U.PermanentCreditsControl = L.Control.extend({ + + options: { + position: 'bottomleft' + }, + + initialize: function (map, options) { + this.map = map; + L.Control.prototype.initialize.call(this, options); + }, + + onAdd: function () { + var paragraphContainer = L.DomUtil.create('div', 'umap-permanent-credits-container'), + creditsParagraph = L.DomUtil.create('p', '', paragraphContainer); + + this.paragraphContainer = paragraphContainer; + this.setCredits(); + this.setBackground(); + + return paragraphContainer; + }, + + setCredits: function () { + this.paragraphContainer.innerHTML = L.Util.toHTML(this.map.options.permanentCredit); + }, + + setBackground: function () { + if (this.map.options.permanentCreditBackground) { + this.paragraphContainer.style.backgroundColor = '#FFFFFFB0'; + } else { + this.paragraphContainer.style.backgroundColor = ''; + } + } + +}); + + L.U.DataLayersControl = L.Control.extend({ options: { @@ -695,7 +732,7 @@ L.U.Map.include({ var build = function () { ul.innerHTML = ''; datalayer.eachFeature(function (feature) { - if (filterValue && !feature.matchFilter(filterValue, filterKeys)) return; + if ((filterValue && !feature.matchFilter(filterValue, filterKeys)) || feature.properties.isVisible === false) return; ul.appendChild(addFeature(feature)); }); }; @@ -732,6 +769,114 @@ L.U.Map.include({ label.textContent = label.title = L._('About'); L.DomEvent.on(link, 'click', this.displayCaption, this); this.ui.openPanel({data: {html: browserContainer}, actions: [link]}); + }, + + _openFilter: function () { + var filterContainer = L.DomUtil.create('div', 'umap-filter-data'), + title = L.DomUtil.add('h3', 'umap-filter-title', filterContainer, this.options.name), + propertiesContainer = L.DomUtil.create('div', 'umap-filter-properties', filterContainer), + advancedFilterKeys = this.getAdvancedFilterKeys(); + + var advancedFiltersFull = {}; + var filtersAlreadyLoaded = true; + if (!this.getMap().options.advancedFilters) { + this.getMap().options.advancedFilters = {}; + filtersAlreadyLoaded = false; + } + advancedFilterKeys.forEach(property => { + advancedFiltersFull[property] = []; + if (!filtersAlreadyLoaded || !this.getMap().options.advancedFilters[property]) { + this.getMap().options.advancedFilters[property] = []; + } + }); + this.eachDataLayer(function (datalayer) { + datalayer.eachFeature(function (feature) { + advancedFilterKeys.forEach(property => { + if (feature.properties[property]) { + if (!advancedFiltersFull[property].includes(feature.properties[property])) { + advancedFiltersFull[property].push(feature.properties[property]); + } + } + }); + }); + }); + + var addPropertyValue = function (property, value) { + var property_li = L.DomUtil.create('li', ''), + filter_check = L.DomUtil.create('input', '', property_li), + filter_label = L.DomUtil.create('label', '', property_li); + filter_check.type = 'checkbox'; + filter_check.id = `checkbox_${property}_${value}`; + filter_check.checked = this.getMap().options.advancedFilters[property] && this.getMap().options.advancedFilters[property].includes(value); + filter_check.setAttribute('data-property', property); + filter_check.setAttribute('data-value', value); + filter_label.htmlFor = `checkbox_${property}_${value}`; + filter_label.innerHTML = value; + L.DomEvent.on(filter_check, 'change', function (e) { + var property = e.srcElement.dataset.property; + var value = e.srcElement.dataset.value; + if (e.srcElement.checked) { + this.getMap().options.advancedFilters[property].push(value); + } else { + this.getMap().options.advancedFilters[property].splice(this.getMap().options.advancedFilters[property].indexOf(value), 1); + } + L.bind(filterFeatures, this)(); + }, this); + return property_li + }; + + var addProperty = function (property) { + var container = L.DomUtil.create('div', 'property-container', propertiesContainer), + headline = L.DomUtil.add('h5', '', container, property); + var ul = L.DomUtil.create('ul', '', container); + var orderedValues = advancedFiltersFull[property]; + orderedValues.sort(); + orderedValues.forEach(value => { + ul.appendChild(L.bind(addPropertyValue, this)(property, value)); + }); + }; + + var filterFeatures = function () { + var noResults = true; + this.eachDataLayer(function (datalayer) { + datalayer.eachFeature(function (feature) { + feature.properties.isVisible = true; + for (const [property, values] of Object.entries(this.map.options.advancedFilters)) { + if (values.length > 0) { + if (!feature.properties[property] || !values.includes(feature.properties[property])) { + feature.properties.isVisible = false; + } + } + } + if (feature.properties.isVisible) { + noResults = false; + if (!this.isLoaded()) this.fetchData(); + this.map.addLayer(feature); + this.fire('show'); + } else { + this.map.removeLayer(feature); + this.fire('hide'); + } + }); + }); + if (noResults) { + this.help.show('advancedFiltersNoResults'); + } else { + this.help.hide(); + } + }; + + propertiesContainer.innerHTML = ''; + advancedFilterKeys.forEach(property => { + L.bind(addProperty, this)(property); + }); + + var link = L.DomUtil.create('li', ''); + L.DomUtil.create('i', 'umap-icon-16 umap-caption', link); + var label = L.DomUtil.create('span', '', link); + label.textContent = label.title = L._('About'); + L.DomEvent.on(link, 'click', this.displayCaption, this); + this.ui.openPanel({ data: { html: filterContainer }, actions: [link] }); } }); @@ -805,12 +950,14 @@ L.U.AttributionControl = L.Control.Attribution.extend({ if (this._map.options.shortCredit) { L.DomUtil.add('span', '', this._container, ' — ' + L.Util.toHTML(this._map.options.shortCredit)); } - var link = L.DomUtil.add('a', '', this._container, ' — ' + L._('About')); - L.DomEvent - .on(link, 'click', L.DomEvent.stop) - .on(link, 'click', this._map.displayCaption, this._map) - .on(link, 'dblclick', L.DomEvent.stop); - if (window.top === window.self) { + if (this._map.options.captionMenus) { + var link = L.DomUtil.add('a', '', this._container, ' — ' + L._('About')); + L.DomEvent + .on(link, 'click', L.DomEvent.stop) + .on(link, 'click', this._map.displayCaption, this._map) + .on(link, 'dblclick', L.DomEvent.stop); + } + if (window.top === window.self && this._map.options.captionMenus) { // We are not in iframe mode var home = L.DomUtil.add('a', '', this._container, ' — ' + L._('Home')); home.href = '/'; @@ -1001,7 +1148,8 @@ L.U.IframeExporter = L.Evented.extend({ embedControl: null, datalayersControl: true, onLoadPanel: 'none', - captionBar: false + captionBar: false, + captionMenus: true }, dimensions: { diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 02afe2cd..30aaa4cb 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -452,9 +452,12 @@ L.U.Help = L.Class.extend({ fillColor: L._('Optional. Same as color if not set.'), shortCredit: L._('Will be displayed in the bottom right corner of the map'), longCredit: L._('Will be visible in the caption of the map'), + permanentCredit: L._('Will be permanently visible in the bottom left corner of the map'), sortKey: L._('Property to use for sorting features'), slugKey: L._('The name of the property to use as feature unique identifier.'), filterKey: L._('Comma separated list of properties to use when filtering features'), + advancedFilterKey: L._('Comma separated list of properties to use for checkbox filtering'), + advancedFiltersNoResults: L._('No results for these filters'), interactive: L._('If false, the polygon will act as a part of the underlying map.'), outlink: L._('Define link to open in a new window on polygon click.'), dynamicRemoteData: L._('Fetch data each time map view changes.'), diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index 88c03703..b3a37733 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -287,7 +287,8 @@ L.FormBuilder.onLoadPanel = L.FormBuilder.Select.extend({ selectOptions: [ ['none', L._('None')], ['caption', L._('Caption')], - ['databrowser', L._('Data browser')] + ['databrowser', L._('Data browser')], + ['datafilters', L._('Data filters')] ] }); @@ -758,6 +759,7 @@ L.U.FormBuilder = L.FormBuilder.extend({ onLoadPanel: {handler: 'onLoadPanel', label: L._('Do you want to display a panel on load?')}, displayPopupFooter: {handler: 'Switch', label: L._('Do you want to display popup footer?')}, captionBar: {handler: 'Switch', label: L._('Do you want to display a caption bar?')}, + captionMenus: {handler: 'Switch', label: L._('Do you want to display caption menus?')}, zoomTo: {handler: 'IntInput', placeholder: L._('Inherit'), helpEntries: 'zoomTo', label: L._('Default zoom level'), inheritable: true}, showLabel: {handler: 'LabelChoice', label: L._('Display label'), inheritable: true}, labelDirection: {handler: 'LabelDirection', label: L._('Label direction'), inheritable: true}, diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 92ca44a2..7365afac 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -43,10 +43,12 @@ L.Map.mergeOptions({ ], moreControl: true, captionBar: false, + captionMenus: true, slideshow: {}, clickable: true, easing: true, - permissions: {} + permissions: {}, + permanentCreditBackground: true, }); L.U.Map.include({ @@ -88,6 +90,7 @@ L.U.Map.include({ L.Util.setBooleanFromQueryString(this.options, 'displayDataBrowserOnLoad'); L.Util.setBooleanFromQueryString(this.options, 'displayCaptionOnLoad'); L.Util.setBooleanFromQueryString(this.options, 'captionBar'); + L.Util.setBooleanFromQueryString(this.options, 'captionMenus'); for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) { L.Util.setNullableBooleanFromQueryString(this.options, this.HIDDABLE_CONTROLS[i] + 'Control'); } @@ -210,6 +213,7 @@ L.U.Map.include({ this.onceDatalayersLoaded(function () { if (this.options.onLoadPanel === 'databrowser') this.openBrowser(); else if (this.options.onLoadPanel === 'caption') this.displayCaption(); + else if (this.options.onLoadPanel === 'datafilters') this.openFilter(); }); this.onceDataLoaded(function () { const slug = L.Util.queryString('feature'); @@ -273,6 +277,7 @@ L.U.Map.include({ this._controls.measure = (new L.MeasureControl()).initHandler(this); this._controls.more = new L.U.MoreControls(); this._controls.scale = L.control.scale(); + this._controls.permanentCredit = new L.U.PermanentCreditsControl(this); if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable(); else this.scrollWheelZoom.disable(); this.renderControls(); @@ -305,6 +310,7 @@ L.U.Map.include({ if (status === undefined || status === null) L.DomUtil.addClass(control._container, 'display-on-more'); else L.DomUtil.removeClass(control._container, 'display-on-more'); } + if (this.options.permanentCredit) this._controls.permanentCredit.addTo(this); if (this.options.moreControl) this._controls.more.addTo(this); if (this.options.scaleControl) this._controls.scale.addTo(this); }, @@ -625,7 +631,8 @@ L.U.Map.include({ 'queryString.miniMap', 'queryString.scaleControl', 'queryString.onLoadPanel', - 'queryString.captionBar' + 'queryString.captionBar', + 'queryString.captionMenus' ]; for (var i = 0; i < this.HIDDABLE_CONTROLS.length; i++) { UIFields.push('queryString.' + this.HIDDABLE_CONTROLS[i] + 'Control'); @@ -893,6 +900,12 @@ L.U.Map.include({ }); }, + openFilter: function () { + this.onceDatalayersLoaded(function () { + this._openFilter(); + }); + }, + displayCaption: function () { var container = L.DomUtil.create('div', 'umap-caption'), title = L.DomUtil.create('h3', '', container); @@ -948,10 +961,19 @@ L.U.Map.include({ umapCredit.innerHTML = L._('Powered by Leaflet and Django, glued by uMap project.', urls); var browser = L.DomUtil.create('li', ''); L.DomUtil.create('i', 'umap-icon-16 umap-list', browser); - var label = L.DomUtil.create('span', '', browser); - label.textContent = label.title = L._('Browse data'); + var labelBrowser = L.DomUtil.create('span', '', browser); + labelBrowser.textContent = labelBrowser.title = L._('Browse data'); L.DomEvent.on(browser, 'click', this.openBrowser, this); - this.ui.openPanel({data: {html: container}, actions: [browser]}); + var actions = [browser]; + if (this.options.advancedFilterKey) { + var filter = L.DomUtil.create('li', ''); + L.DomUtil.create('i', 'umap-icon-16 umap-add', filter); + var labelFilter = L.DomUtil.create('span', '', filter); + labelFilter.textContent = labelFilter.title = L._('Select data'); + L.DomEvent.on(filter, 'click', this.openFilter, this); + actions.push(filter) + } + this.ui.openPanel({data: {html: container}, actions: actions}); }, eachDataLayer: function (method, context) { @@ -1053,16 +1075,20 @@ L.U.Map.include({ 'popupContentTemplate', 'zoomTo', 'captionBar', + 'captionMenus', 'slideshow', 'sortKey', 'labelKey', 'filterKey', + 'advancedFilterKey', 'slugKey', 'showLabel', 'labelDirection', 'labelInteractive', 'shortCredit', 'longCredit', + 'permanentCredit', + 'permanentCreditBackground', 'zoomControl', 'datalayersControl', 'searchControl', @@ -1215,10 +1241,14 @@ L.U.Map.include({ 'options.scaleControl', 'options.onLoadPanel', 'options.displayPopupFooter', - 'options.captionBar' + 'options.captionBar', + 'options.captionMenus' ]); builder = new L.U.FormBuilder(this, UIFields, { - callback: this.renderControls, + callback: function() { + this.renderControls(); + this.initCaptionBar(); + }, callbackContext: this }); var controlsOptions = L.DomUtil.createFieldset(container, L._('User interface options')); @@ -1253,11 +1283,13 @@ L.U.Map.include({ 'options.labelKey', ['options.sortKey', {handler: 'BlurInput', helpEntries: 'sortKey', placeholder: L._('Default: name'), label: L._('Sort key'), inheritable: true}], ['options.filterKey', {handler: 'Input', helpEntries: 'filterKey', placeholder: L._('Default: name'), label: L._('Filter keys'), inheritable: true}], + ['options.advancedFilterKey', {handler: 'Input', helpEntries: 'advancedFilterKey', placeholder: L._('Example: key1,key2,key3'), label: L._('Advanced filter keys'), inheritable: true}], ['options.slugKey', {handler: 'BlurInput', helpEntries: 'slugKey', placeholder: L._('Default: name'), label: L._('Feature identifier key')}] ]; builder = new L.U.FormBuilder(this, optionsFields, { callback: function (e) { + this.initCaptionBar(); this.eachDataLayer(function (datalayer) { if (e.helper.field === 'options.sortKey') datalayer.reindex(); datalayer.redraw(); @@ -1365,10 +1397,12 @@ L.U.Map.include({ var creditsFields = [ ['options.licence', {handler: 'LicenceChooser', label: L._('licence')}], ['options.shortCredit', {handler: 'Input', label: L._('Short credits'), helpEntries: ['shortCredit', 'textFormatting']}], - ['options.longCredit', {handler: 'Textarea', label: L._('Long credits'), helpEntries: ['longCredit', 'textFormatting']}] + ['options.longCredit', {handler: 'Textarea', label: L._('Long credits'), helpEntries: ['longCredit', 'textFormatting']}], + ['options.permanentCredit', {handler: 'Textarea', label: L._('Permanent credits'), helpEntries: ['permanentCredit', 'textFormatting']}], + ['options.permanentCreditBackground', {handler: 'Switch', label: L._('Permanent credits background')}] ]; var creditsBuilder = new L.U.FormBuilder(this, creditsFields, { - callback: function () {this._controls.attribution._update();}, + callback: this.renderControls, callbackContext: this }); credits.appendChild(creditsBuilder.build()); @@ -1428,13 +1462,21 @@ L.U.Map.include({ name = L.DomUtil.create('h3', '', container); L.DomEvent.disableClickPropagation(container); this.permissions.addOwnerLink('span', container); - var about = L.DomUtil.add('a', 'umap-about-link', container, ' — ' + L._('About')); - about.href = '#'; - L.DomEvent.on(about, 'click', this.displayCaption, this); - var browser = L.DomUtil.add('a', 'umap-open-browser-link', container, ' | ' + L._('Browse data')); - browser.href = '#'; - L.DomEvent.on(browser, 'click', L.DomEvent.stop) - .on(browser, 'click', this.openBrowser, this); + if (this.options.captionMenus) { + var about = L.DomUtil.add('a', 'umap-about-link', container, ' — ' + L._('About')); + about.href = '#'; + L.DomEvent.on(about, 'click', this.displayCaption, this); + var browser = L.DomUtil.add('a', 'umap-open-browser-link', container, ' | ' + L._('Browse data')); + browser.href = '#'; + L.DomEvent.on(browser, 'click', L.DomEvent.stop) + .on(browser, 'click', this.openBrowser, this); + if (this.options.advancedFilterKey) { + var filter = L.DomUtil.add('a', 'umap-open-filter-link', container, ' | ' + L._('Select data')); + filter.href = '#'; + L.DomEvent.on(filter, 'click', L.DomEvent.stop) + .on(filter, 'click', this.openFilter, this); + } + } var setName = function () { name.textContent = this.getDisplayName(); }; @@ -1615,11 +1657,17 @@ L.U.Map.include({ }); } } - items.push('-', - { - text: L._('Browse data'), - callback: this.openBrowser - }, + items.push('-', { + text: L._('Browse data'), + callback: this.openBrowser + }); + if (this.options.advancedFilterKey) { + items.push({ + text: L._('Select data'), + callback: this.openFilter + }) + } + items.push( { text: L._('About'), callback: this.displayCaption @@ -1698,6 +1746,10 @@ L.U.Map.include({ getFilterKeys: function () { return (this.options.filterKey || this.options.sortKey || 'name').split(','); + }, + + getAdvancedFilterKeys: function () { + return (this.options.advancedFilterKey || '').split(","); } }); diff --git a/umap/static/umap/locale/fr.js b/umap/static/umap/locale/fr.js index 08d54ad8..bce3005f 100644 --- a/umap/static/umap/locale/fr.js +++ b/umap/static/umap/locale/fr.js @@ -262,6 +262,7 @@ var locale = { "See all": "Tout voir", "See data layers": "Voir les calques", "See full screen": "Voir en plein écran", + "Select data": "Sélectionner les données", "Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…": "Désactiver pour masquer ce calque du diaporama, du navigateur de données…", "Shape properties": "Propriétés de la forme", "Short URL": "URL courte", diff --git a/umap/static/umap/locale/fr.json b/umap/static/umap/locale/fr.json index 9259f226..a0fb23b8 100644 --- a/umap/static/umap/locale/fr.json +++ b/umap/static/umap/locale/fr.json @@ -262,6 +262,7 @@ "See all": "Tout voir", "See data layers": "Voir les calques", "See full screen": "Voir en plein écran", + "Select data": "Sélectionner les données", "Set it to false to hide this layer from the slideshow, the data browser, the popup navigation…": "Désactiver pour masquer ce calque du diaporama, du navigateur de données…", "Shape properties": "Propriétés de la forme", "Short URL": "URL courte", diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css index 707821c7..91371c83 100644 --- a/umap/static/umap/map.css +++ b/umap/static/umap/map.css @@ -117,6 +117,12 @@ a.umap-control-text { .leaflet-control-edit-enable a:hover { background-color: #4d5759; } +.umap-permanent-credits-container { + max-width: 20rem; + margin-left: 5px!important; + margin-bottom: 5px!important; + padding: 0.5rem; +} @@ -729,20 +735,24 @@ a.add-datalayer:hover, margin-bottom: 14px; border-radius: 2px; } +.umap-browse-features h5, .umap-filter-data h5 { + margin-bottom: 0; + overflow: hidden; + padding-left: 5px; +} .umap-browse-features h5 { height: 30px; line-height: 30px; background-color: #eeeee0; - margin-bottom: 0; color: #666; - overflow: hidden; - padding-left: 5px; } .umap-browse-features h5 span { margin-left: 10px; } .umap-browse-features li { padding: 2px 0; +} +.umap-browse-features li, .umap-filter-data li { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -775,6 +785,9 @@ a.add-datalayer:hover, .umap-browse-features .polygon .feature-color { background-position: -32px -16px; } +.umap-filter-data .property-container:not(:first-child) { + margin-top: 14px; +} .show-on-edit { display: none!important; } @@ -1359,6 +1372,10 @@ a.add-datalayer:hover, .leaflet-control-layers-expanded { margin-left: 10px; } + + .umap-permanent-credits-container { + max-width: 100%; + } } /* ****** */