diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css
index b7f11b0e..f7c9969a 100644
--- a/umap/static/umap/base.css
+++ b/umap/static/umap/base.css
@@ -159,6 +159,51 @@ input[type="range"] {
input[type="checkbox"] {
margin: 0 5px;
vertical-align: middle;
+ appearance: none;
+}
+input[type="checkbox"]:after {
+ display: inline-block;
+ content: ' ';
+ width: 12px;
+ height: 12px;
+ border: 1px solid var(--color-lightGray);
+ cursor: pointer;
+ text-align: center;
+ font-size: 1.3rem;
+ line-height: 1rem;
+}
+input[type=checkbox]:checked:after {
+ background-color: var(--color-lightCyan);
+ content: '✓';
+}
+label input[type="radio"] {
+ appearance: none;
+ margin-right: 10px;
+}
+input[type="radio"]:after {
+ display: inline-block;
+ content: ' ';
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: 1px solid var(--color-lightGray);
+ cursor: pointer;
+ text-align: center;
+ font-size: 1.3rem;
+ line-height: 1rem;
+ vertical-align: bottom;
+}
+label input[type="radio"]:checked:after {
+ background-color: var(--color-lightCyan);
+ content: '•';
+ font-size: 3rem;
+ line-height: 1.1rem;
+ color: var(--color-darkGray);
+}
+
+input[data-modified=true] {
+ background-color: var(--color-lightCyan);
+ border: 1px solid var(--color-darkGray);
}
textarea {
height: inherit;
@@ -263,9 +308,6 @@ input[type="checkbox"] + label {
display: inline;
padding: 0 14px;
}
-label input[type="radio"] {
- margin-right: 10px;
-}
select + .error,
input + .error {
display: block;
@@ -290,67 +332,59 @@ input:invalid {
border-color: #1b1f20;
color: #efefef;
}
-.fieldset {
+details {
margin-bottom: 5px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
-.dark .fieldset {
+.dark details {
border: 1px solid #222;
}
-.fieldset .fields {
- visibility: hidden;
- opacity: 0;
- transition: visibility 0s, opacity 0.5s linear;
- height: 0;
+details fieldset {
overflow: hidden;
+ border: 1px solid var(--color-lightGray);
+ margin: 0;
+ padding-top: 10px;
}
-.fieldset.toggle.on .fields {
- visibility: visible;
- opacity: 1;
- height: initial;
- padding: 10px;
-}
-.fieldset.toggle .legend {
- text-align: left;
- display: block;
+details summary {
cursor: pointer;
background-color: var(--color-lightGray);
- height: 30px;
line-height: 30px;
- margin: 0;
- font-family: fira_sans;
- font-weight: normal;
font-size: 1.2em;
padding: 0 5px;
}
-.dark .fieldset.toggle .legend {
+.dark details summary {
background-color: #232729;
color: #fff;
}
-.fieldset.toggle .legend:before {
- background-repeat: no-repeat;
- text-indent: 24px;
- height: 24px;
- width: 24px;
- line-height: 24px;
- display: inline-block;
- background-image: url('./img/16.svg');
- vertical-align: bottom;
- content: " ";
- background-position: -144px -76px;
-}
-.dark .fieldset.toggle .legend:before {
- background-image: url('./img/16-white.svg');
-}
-.fieldset.toggle.on .legend:before {
- background-position: -144px -51px;
+.dark details fieldset {
+ border: 1px solid var(--color-darkGray);
}
fieldset legend {
font-size: 1.1rem;
padding: 0 5px;
}
+[data-badge] {
+ position: relative;
+}
+[data-badge]:after {
+ position: absolute;
+ right: -6px;
+ top: -6px;
+ min-width: 8px;
+ min-height: 8px;
+ line-height: 8px;
+ padding: 2px;
+ font-weight: bold;
+ background-color: var(--color-accent);
+ color: var(--color-darkBlue);
+ text-align: center;
+ font-size: .75rem;
+ border-radius: 50%;
+ content: attr(data-badge);
+}
+
/* Switch */
input.switch:empty {
display: none;
@@ -408,11 +442,16 @@ input.switch:checked:empty ~ label:after {
}
.dark input.switch:checked ~ label:before,
input.switch:checked ~ label:before {
- background-color: #215d9c;
+ background-color: var(--color-lightCyan);
+ border: 1px solid var(--color-lightGray);
+ color: var(--color-darkGray);
content: "ON";
text-indent: 0.7em;
text-align: left;
font-weight: bold;
+.dark input.switch:checked ~ label:before {
+ border: none;
+}
}
input.switch:checked ~ label:after {
margin-left: 3em;
@@ -458,9 +497,9 @@ input.switch:checked ~ label:after {
background-color: #2c3233;
}
.umap-multiplechoice input[type='radio']:checked + label {
- background-color: #215d9c;
+ background-color: var(--color-lightCyan);
box-shadow: inset 0 0 6px 0px #2c3233;
- color: #ededed;
+ color: var(--color-darkGray);
}
.inheritable .header,
.inheritable {
diff --git a/umap/static/umap/css/icon.css b/umap/static/umap/css/icon.css
index 2bf10a74..59a73aaa 100644
--- a/umap/static/umap/css/icon.css
+++ b/umap/static/umap/css/icon.css
@@ -55,6 +55,9 @@
.off .icon-edit {
background-position: -51px -73px;
}
+.icon-filters {
+ background-position: -4px -24px;
+}
.icon-key {
background-position: -144px -121px;
}
@@ -76,6 +79,9 @@
.icon-resize {
background-position: -74px -144px;
}
+.icon-restore {
+ background-position: -121px -74px;
+}
.expanded .icon-resize {
background-position: -50px -144px;
}
diff --git a/umap/static/umap/css/panel.css b/umap/static/umap/css/panel.css
index 9759de54..80f168b7 100644
--- a/umap/static/umap/css/panel.css
+++ b/umap/static/umap/css/panel.css
@@ -6,7 +6,7 @@
bottom: var(--panel-bottom);
overflow-x: auto;
z-index: 1010;
- background-color: #fff;
+ background-color: var(--background-color);
opacity: 0.98;
cursor: initial;
border-radius: 5px;
@@ -14,7 +14,6 @@
}
.panel.dark {
border: 1px solid #222;
- background-color: var(--color-darkGray);
color: #efefef;
}
.panel.full {
diff --git a/umap/static/umap/img/16.svg b/umap/static/umap/img/16.svg
index 4cf58b6b..d30369be 100644
--- a/umap/static/umap/img/16.svg
+++ b/umap/static/umap/img/16.svg
@@ -28,7 +28,6 @@
-
@@ -36,7 +35,8 @@
-
+
+
diff --git a/umap/static/umap/img/source/16.svg b/umap/static/umap/img/source/16.svg
index 494ee015..c7569e88 100644
--- a/umap/static/umap/img/source/16.svg
+++ b/umap/static/umap/img/source/16.svg
@@ -10,7 +10,7 @@
-
+
@@ -46,7 +46,6 @@
-
@@ -55,7 +54,8 @@
-
+
+
diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js
index f68dc457..3c147a37 100644
--- a/umap/static/umap/js/modules/browser.js
+++ b/umap/static/umap/js/modules/browser.js
@@ -9,11 +9,22 @@ export default class Browser {
filter: '',
inBbox: false,
}
+ this._mode = 'layers'
+ }
+
+ set mode(value) {
+ // Store the mode so we can respect it when we redraw
+ if (['data', 'filters'].includes(value)) this.map.panel.mode = 'expanded'
+ else if (value === 'layers') this.map.panel.mode = 'condensed'
+ this._mode = value
+ }
+
+ get mode() {
+ return this._mode
}
addFeature(feature, parent) {
- const filter = this.options.filter
- if (filter && !feature.matchFilter(filter, this.filterKeys)) return
+ if (feature.isFiltered()) return
if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return
const row = DomUtil.create('li', `${feature.getClassName()} feature`)
const zoom_to = DomUtil.createButtonIcon(
@@ -98,15 +109,29 @@ export default class Browser {
counter.title = translate(`Features in this layer: ${count}`)
}
+ toggleBadge() {
+ U.Utils.toggleBadge(this.filtersTitle, this.hasFilters())
+ U.Utils.toggleBadge('.umap-control-browse', this.hasFilters())
+ }
+
onFormChange() {
this.map.eachBrowsableDataLayer((datalayer) => {
datalayer.resetLayer(true)
this.updateDatalayer(datalayer)
})
+ this.toggleBadge()
+ }
+
+ redraw() {
+ if (this.isOpen()) this.open()
}
isOpen() {
- return !!document.querySelector('.umap-browser')
+ return !!document.querySelector('.on .umap-browser')
+ }
+
+ hasFilters() {
+ return !!this.options.filter || this.map.facets.isActive()
}
onMoveEnd() {
@@ -126,7 +151,9 @@ export default class Browser {
})
}
- open() {
+ open(mode) {
+ // Force only if mode is known, otherwise keep current mode.
+ if (mode) this.mode = mode
// Get once but use it for each feature later
this.filterKeys = this.map.getFilterKeys()
const container = DomUtil.create('div')
@@ -135,18 +162,46 @@ export default class Browser {
DomEvent.disableClickPropagation(container)
DomUtil.createTitle(container, translate('Browse data'), 'icon-layers')
- this.tabsMenu(container, 'browse')
- const formContainer = DomUtil.create('div', '', container)
+ const formContainer = DomUtil.createFieldset(container, L._('Filters'), {
+ on: this.mode === 'filters',
+ className: 'filters',
+ icon: 'icon-filters',
+ })
+ this.filtersTitle = container.querySelector('summary')
+ this.toggleBadge()
this.dataContainer = DomUtil.create('div', '', container)
- const fields = [
- ['options.filter', { handler: 'Input', placeholder: translate('Filter') }],
+ let fields = [
+ [
+ 'options.filter',
+ { handler: 'Input', placeholder: translate('Search map features…') },
+ ],
['options.inBbox', { handler: 'Switch', label: translate('Current map view') }],
]
const builder = new L.FormBuilder(this, fields, {
callback: () => this.onFormChange(),
})
+ let filtersBuilder
formContainer.appendChild(builder.build())
+ DomEvent.on(builder.form, 'reset', () => {
+ window.setTimeout(builder.syncAll.bind(builder))
+ })
+ if (this.map.options.facetKey) {
+ fields = this.map.facets.build()
+ filtersBuilder = new L.FormBuilder(this.map.facets, fields, {
+ callback: () => this.onFormChange(),
+ })
+ DomEvent.on(filtersBuilder.form, 'reset', () => {
+ window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
+ })
+ formContainer.appendChild(filtersBuilder.build())
+ }
+ const reset = DomUtil.createButton('flat', formContainer, '', () => {
+ builder.form.reset()
+ if (filtersBuilder) filtersBuilder.form.reset()
+ })
+ DomUtil.createIcon(reset, 'icon-restore')
+ DomUtil.element({ tagName: 'span', parent: reset, textContent: translate('Reset all') })
this.map.panel.open({
content: container,
@@ -166,18 +221,4 @@ export default class Browser {
DomEvent.on(button, 'click', map.openBrowser, map)
return button
}
-
- tabsMenu(container, active) {
- const tabs = L.DomUtil.create('div', 'flat-tabs', container)
- const browse = L.DomUtil.add('button', 'flat tab-browse', tabs, L._('Data'))
- DomEvent.on(browse, 'click', this.open, this)
- if (this.map.options.facetKey) {
- const facets = L.DomUtil.add('button', 'flat tab-facets', tabs, L._('Filters'))
- DomEvent.on(facets, 'click', this.map.facets.open, this.map.facets)
- }
- const info = L.DomUtil.add('button', 'flat tab-info', tabs, L._('About'))
- DomEvent.on(info, 'click', this.map.displayCaption, this.map)
- let el = tabs.querySelector(`.tab-${active}`)
- L.DomUtil.addClass(el, 'on')
- }
}
diff --git a/umap/static/umap/js/modules/facets.js b/umap/static/umap/js/modules/facets.js
index 00900b7b..461a8ec3 100644
--- a/umap/static/umap/js/modules/facets.js
+++ b/umap/static/umap/js/modules/facets.js
@@ -10,15 +10,18 @@ export default class Facets {
compute(names, defined) {
const properties = {}
+ let selected
names.forEach((name) => {
const type = defined[name]['type']
properties[name] = { type: type }
- this.selected[name] = { type: type }
+ selected = this.selected[name] || {}
+ selected.type = type
if (!['date', 'datetime', 'number'].includes(type)) {
properties[name].choices = []
- this.selected[name].choices = []
+ selected.choices = selected.choices || []
}
+ this.selected[name] = selected
})
this.map.eachBrowsableDataLayer((datalayer) => {
@@ -53,42 +56,20 @@ export default class Facets {
return properties
}
- redraw() {
- if (this.isOpen()) this.open()
+ isActive() {
+ for (let { type, min, max, choices } of Object.values(this.selected)) {
+ if (min !== undefined || max != undefined || choices?.length) {
+ return true
+ }
+ }
+ return false
}
- isOpen() {
- return !!document.querySelector('.umap-facet-search')
- }
-
- open() {
- const container = L.DomUtil.create('div', 'umap-facet-search')
- const title = L.DomUtil.add(
- 'h3',
- 'umap-filter-title',
- container,
- translate('Facet search')
- )
- this.map.browser.tabsMenu(container, 'facets')
+ build() {
const defined = this.getDefined()
const names = Object.keys(defined)
const facetProperties = this.compute(names, defined)
- const filterFeatures = function () {
- let found = false
- this.map.eachBrowsableDataLayer((datalayer) => {
- datalayer.resetLayer(true)
- if (datalayer.hasDataVisible()) found = true
- })
- // TODO: display a results counter in the panel instead.
- if (!found) {
- this.map.ui.alert({
- content: translate('No results for these facets'),
- level: 'info',
- })
- }
- }
-
const fields = names.map((name) => {
let criteria = facetProperties[name]
let handler = 'FacetSearchChoices'
@@ -114,13 +95,7 @@ export default class Facets {
]
})
- const builder = new L.FormBuilder(this, fields, {
- callback: filterFeatures,
- callbackContext: this,
- })
- container.appendChild(builder.build())
-
- this.map.panel.open({ content: container })
+ return fields
}
getDefined() {
diff --git a/umap/static/umap/js/modules/schema.js b/umap/static/umap/js/modules/schema.js
index 504708b5..3db6f726 100644
--- a/umap/static/umap/js/modules/schema.js
+++ b/umap/static/umap/js/modules/schema.js
@@ -272,9 +272,9 @@ export const SCHEMA = {
choices: [
['none', translate('None')],
['caption', translate('Caption')],
- ['databrowser', translate('Data browser')],
- ['datalayers', translate('Layers')],
- ['facet', translate('Facet search')],
+ ['databrowser', translate('Browser in data mode')],
+ ['datalayers', translate('Browser in layers mode')],
+ ['datafilters', translate('Browser in filters mode')],
],
default: 'none',
},
diff --git a/umap/static/umap/js/modules/utils.js b/umap/static/umap/js/modules/utils.js
index f5c36a31..2f0cb57a 100644
--- a/umap/static/umap/js/modules/utils.js
+++ b/umap/static/umap/js/modules/utils.js
@@ -362,3 +362,11 @@ export function parseNaiveDate(value) {
// Let's pretend naive date are UTC, and remove time…
return new Date(Date.UTC(naive.getFullYear(), naive.getMonth(), naive.getDate()))
}
+
+export function toggleBadge(element, value) {
+ if (!element.nodeType) element = document.querySelector(element)
+ if (!element) return
+ // True means simple badge, without content
+ if (value) element.dataset.badge = value === true ? ' ' : value
+ else delete element.dataset.badge
+}
diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js
index a104cfc9..770bcda5 100644
--- a/umap/static/umap/js/umap.controls.js
+++ b/umap/static/umap/js/umap.controls.js
@@ -499,8 +499,11 @@ L.Control.Button = L.Control.extend({
this
)
L.DomEvent.on(button, 'dblclick', L.DomEvent.stopPropagation)
+ this.afterAdd(container)
return container
},
+
+ afterAdd: function (container) {},
})
U.DataLayersControl = L.Control.Button.extend({
@@ -510,6 +513,10 @@ U.DataLayersControl = L.Control.Button.extend({
title: L._('See layers'),
},
+ afterAdd: function (container) {
+ U.Utils.toggleBadge(container, this.map.browser.hasFilters())
+ },
+
onClick: function () {
this.map.openBrowser()
},
@@ -663,15 +670,11 @@ const ControlsMixin = {
'star',
'tilelayers',
],
- _openFacet: function () {
- this.facets.open()
- },
displayCaption: function () {
const container = L.DomUtil.create('div', 'umap-caption')
L.DomUtil.createTitle(container, this.options.name, 'icon-caption')
this.permissions.addOwnerLink('h5', container)
- this.browser.tabsMenu(container, 'info')
if (this.options.description) {
const description = L.DomUtil.element({
tagName: 'div',
diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js
index 156d15c7..2180b05c 100644
--- a/umap/static/umap/js/umap.core.js
+++ b/umap/static/umap/js/umap.core.js
@@ -81,18 +81,18 @@ L.DomUtil.add = (tagName, className, container, content) => {
L.DomUtil.createFieldset = (container, legend, options) => {
options = options || {}
- const fieldset = L.DomUtil.create('div', 'fieldset toggle', container)
- const legendEl = L.DomUtil.add('h5', 'legend style_options_toggle', fieldset, legend)
- const fieldsEl = L.DomUtil.add('div', 'fields with-transition', fieldset)
- L.DomEvent.on(legendEl, 'click', function () {
- if (L.DomUtil.hasClass(fieldset, 'on')) {
- L.DomUtil.removeClass(fieldset, 'on')
- } else {
- L.DomUtil.addClass(fieldset, 'on')
- if (options.callback) options.callback.call(options.context || this)
- }
- })
- return fieldsEl
+ const details = L.DomUtil.create('details', options.className || '', container)
+ const summary = L.DomUtil.add('summary', '', details)
+ if (options.icon) L.DomUtil.createIcon(summary, options.icon)
+ L.DomUtil.add('span', '', summary, legend)
+ const fieldset = L.DomUtil.add('fieldset', '', details)
+ details.open = options.on === true
+ if (options.callback) {
+ L.DomEvent.on(details, 'toggle', () => {
+ if (details.open) options.callback.call(options.context || this)
+ })
+ }
+ return fieldset
}
L.DomUtil.createButton = (className, container, content, callback, context) => {
@@ -556,9 +556,9 @@ U.Help = L.Class.extend({
'Comma separated list of properties to use for sorting features. To reverse the sort, put a minus sign (-) before. Eg. mykey,-otherkey.'
),
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'),
+ filterKey: L._('Comma separated list of properties to use when filtering features by text input'),
facetKey: L._(
- 'Comma separated list of properties to use for facet search (eg.: mykey,otherkey). To control label, add it after a | (eg.: mykey|My Key,otherkey|Other Key). To control input field type, add it after another | (eg.: mykey|My Key|checkbox,otherkey|Other Key|datetime). Allowed values for the input field type are checkbox (default), radio, number, date and datetime.'
+ 'Comma separated list of properties to use for filters (eg.: mykey,otherkey). To control label, add it after a | (eg.: mykey|My Key,otherkey|Other Key). To control input field type, add it after another | (eg.: mykey|My Key|checkbox,otherkey|Other Key|datetime). Allowed values for the input field type are checkbox (default), radio, number, date and datetime.'
),
interactive: L._(
'If false, the polygon or line will act as a part of the underlying map.'
diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js
index 5d4b0d86..7c81921b 100644
--- a/umap/static/umap/js/umap.features.js
+++ b/umap/static/umap/js/umap.features.js
@@ -494,6 +494,14 @@ U.FeatureMixin = {
this.bindTooltip(U.Utils.escapeHTML(displayName), options)
},
+ isFiltered: function () {
+ const filterKeys = this.map.getFilterKeys()
+ const filter = this.map.browser.options.filter
+ if (filter && !this.matchFilter(filter, filterKeys)) return true
+ if (!this.matchFacets()) return true
+ return false
+ },
+
matchFilter: function (filter, keys) {
filter = filter.toLowerCase()
for (let i = 0, value; i < keys.length; i++) {
@@ -513,8 +521,6 @@ U.FeatureMixin = {
case 'date':
case 'datetime':
case 'number':
- min = parser(min)
- max = parser(max)
if (!isNaN(min) && !isNaN(value) && min > value) return false
if (!isNaN(max) && !isNaN(value) && max < value) return false
break
diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js
index 9873b7e4..3297ff7f 100644
--- a/umap/static/umap/js/umap.forms.js
+++ b/umap/static/umap/js/umap.forms.js
@@ -744,7 +744,17 @@ L.FormBuilder.Switch = L.FormBuilder.CheckBox.extend({
},
})
-L.FormBuilder.FacetSearchChoices = L.FormBuilder.Element.extend({
+L.FormBuilder.FacetSearchBase = L.FormBuilder.Element.extend({
+
+ buildLabel: function () {
+ this.label = L.DomUtil.element({
+ tagName: 'legend',
+ textContent: this.options.label,
+ })
+ }
+
+})
+L.FormBuilder.FacetSearchChoices = L.FormBuilder.FacetSearchBase.extend({
build: function () {
this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode)
this.container.appendChild(this.label)
@@ -756,13 +766,6 @@ L.FormBuilder.FacetSearchChoices = L.FormBuilder.Element.extend({
choices.forEach((value) => this.buildLi(value))
},
- buildLabel: function () {
- this.label = L.DomUtil.element({
- tagName: 'legend',
- textContent: this.options.label,
- })
- },
-
buildLi: function (value) {
const property_li = L.DomUtil.create('li', '', this.ul)
const label = L.DomUtil.create('label', '', property_li)
@@ -787,7 +790,7 @@ L.FormBuilder.FacetSearchChoices = L.FormBuilder.Element.extend({
},
})
-L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({
+L.FormBuilder.MinMaxBase = L.FormBuilder.FacetSearchBase.extend({
getInputType: function (type) {
return type
},
@@ -796,7 +799,7 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({
return [L._('Min'), L._('Max')]
},
- castValue: function (value) {
+ prepareForHTML: function (value) {
return value.valueOf()
},
@@ -804,6 +807,10 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({
this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode)
this.container.appendChild(this.label)
const { min, max, type } = this.options.criteria
+ const { min: modifiedMin, max: modifiedMax } = this.get()
+
+ const currentMin = modifiedMin !== undefined ? modifiedMin : min
+ const currentMax = modifiedMax !== undefined ? modifiedMax : max
this.type = type
this.inputType = this.getInputType(this.type)
@@ -815,9 +822,17 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({
this.minInput = L.DomUtil.create('input', '', this.minLabel)
this.minInput.type = this.inputType
this.minInput.step = 'any'
+ this.minInput.min = this.prepareForHTML(min)
+ this.minInput.max = this.prepareForHTML(max)
if (min != null) {
- this.minInput.valueAsNumber = this.castValue(min)
- this.minInput.dataset.value = min
+ // The value stored using setAttribute is not modified by
+ // user input, and will be used as initial value when calling
+ // form.reset(), and can also be retrieve later on by using
+ // getAttributing, to compare with current value and know
+ // if this value has been modified by the user
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
+ this.minInput.setAttribute('value', this.prepareForHTML(min))
+ this.minInput.value = this.prepareForHTML(currentMin)
}
this.maxLabel = L.DomUtil.create('label', '', this.container)
@@ -826,37 +841,76 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({
this.maxInput = L.DomUtil.create('input', '', this.maxLabel)
this.maxInput.type = this.inputType
this.maxInput.step = 'any'
+ this.maxInput.min = this.prepareForHTML(min)
+ this.maxInput.max = this.prepareForHTML(max)
if (max != null) {
- this.maxInput.valueAsNumber = this.castValue(max)
- this.maxInput.dataset.value = max
+ // Cf comment above about setAttribute vs value
+ this.maxInput.setAttribute('value', this.prepareForHTML(max))
+ this.maxInput.value = this.prepareForHTML(currentMax)
}
+ this.toggleStatus()
- L.DomEvent.on(this.minInput, 'change', (e) => this.sync())
- L.DomEvent.on(this.maxInput, 'change', (e) => this.sync())
+ L.DomEvent.on(this.minInput, 'change', () => this.sync())
+ L.DomEvent.on(this.maxInput, 'change', () => this.sync())
},
- buildLabel: function () {
- this.label = L.DomUtil.element({
- tagName: 'legend',
- textContent: this.options.label,
- })
+ toggleStatus: function () {
+ this.minInput.dataset.modified = this.isMinModified()
+ this.maxInput.dataset.modified = this.isMaxModified()
+ },
+
+ sync: function () {
+ L.FormBuilder.Element.prototype.sync.call(this)
+ this.toggleStatus()
+ },
+
+ isMinModified: function () {
+ const default_ = this.minInput.getAttribute("value")
+ const current = this.minInput.value
+ return current != default_
+ },
+
+ isMaxModified: function () {
+ const default_ = this.maxInput.getAttribute("value")
+ const current = this.maxInput.value
+ return current != default_
},
toJS: function () {
- return {
+ const opts = {
type: this.type,
- min: this.minInput.value,
- max: this.maxInput.value,
}
+ if (this.minInput.value !== '' && this.isMinModified()) {
+ opts.min = this.prepareForJS(this.minInput.value)
+ }
+ if (this.maxInput.value !== '' && this.isMaxModified()) {
+ opts.max = this.prepareForJS(this.maxInput.value)
+ }
+ return opts
},
})
-L.FormBuilder.FacetSearchNumber = L.FormBuilder.MinMaxBase.extend({})
+L.FormBuilder.FacetSearchNumber = L.FormBuilder.MinMaxBase.extend({
+ prepareForJS: function (value) {
+ return new Number(value)
+ },
+})
L.FormBuilder.FacetSearchDate = L.FormBuilder.MinMaxBase.extend({
- castValue: function (value) {
- return value.valueOf() - value.getTimezoneOffset() * 60000
+ prepareForJS: function (value) {
+ return new Date(value)
},
+
+ toLocaleDateTime: function (dt) {
+ return new Date(dt.valueOf() - dt.getTimezoneOffset() * 60000)
+ },
+
+ prepareForHTML: function (value) {
+ // Value must be in local time
+ if (isNaN(value)) return
+ return this.toLocaleDateTime(value).toISOString().substr(0, 10)
+ },
+
getLabels: function () {
return [L._('From'), L._('Until')]
},
@@ -866,6 +920,12 @@ L.FormBuilder.FacetSearchDateTime = L.FormBuilder.FacetSearchDate.extend({
getInputType: function (type) {
return 'datetime-local'
},
+
+ prepareForHTML: function (value) {
+ // Value must be in local time
+ if (isNaN(value)) return
+ return this.toLocaleDateTime(value).toISOString().slice(0, -1)
+ },
})
L.FormBuilder.MultiChoice = L.FormBuilder.Element.extend({
diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js
index f11054bb..b37767a5 100644
--- a/umap/static/umap/js/umap.js
+++ b/umap/static/umap/js/umap.js
@@ -149,6 +149,9 @@ U.Map = L.Map.extend({
if (this.options.datalayersControl === 'expanded') {
this.options.onLoadPanel = 'datalayers'
}
+ if (this.options.onLoadPanel === 'facet') {
+ this.options.onLoadPanel = 'datafilters'
+ }
let isDirty = false // self status
try {
@@ -211,15 +214,15 @@ U.Map = L.Map.extend({
if (L.Util.queryString('share')) {
this.share.open()
} else if (this.options.onLoadPanel === 'databrowser') {
- this.openBrowser('expanded')
+ this.openBrowser('data')
} else if (this.options.onLoadPanel === 'datalayers') {
- this.openBrowser('condensed')
+ this.openBrowser('layers')
+ } else if (this.options.onLoadPanel === 'datafilters') {
+ this.panel.mode = 'expanded'
+ this.openBrowser('filters')
} else if (this.options.onLoadPanel === 'caption') {
this.panel.mode = 'condensed'
this.displayCaption()
- } else if (['facet', 'datafilters'].includes(this.options.onLoadPanel)) {
- this.panel.mode = 'expanded'
- this.openFacet()
}
if (L.Util.queryString('edit')) {
if (this.hasEditMode()) this.enableEdit()
@@ -252,7 +255,7 @@ U.Map = L.Map.extend({
this.initCaptionBar()
this.renderEditToolbar()
this.renderControls()
- this.facets.redraw()
+ this.browser.redraw()
break
case 'data':
this.redrawVisibleDataLayers()
@@ -908,15 +911,8 @@ U.Map = L.Map.extend({
},
openBrowser: function (mode) {
- if (mode) this.panel.mode = mode
this.onceDatalayersLoaded(function () {
- this.browser.open()
- })
- },
-
- openFacet: function () {
- this.onceDataLoaded(function () {
- this._openFacet()
+ this.browser.open(mode)
})
},
@@ -1229,7 +1225,7 @@ U.Map = L.Map.extend({
handler: 'Input',
helpEntries: 'filterKey',
placeholder: L._('Default: name'),
- label: L._('Filter keys'),
+ label: L._('Search keys'),
inheritable: true,
},
],
@@ -1239,7 +1235,7 @@ U.Map = L.Map.extend({
handler: 'BlurInput',
helpEntries: 'facetKey',
placeholder: L._('Example: key1,key2|Label 2,key3|Label 3|checkbox'),
- label: L._('Facet keys'),
+ label: L._('Filters keys'),
},
],
[
@@ -1602,15 +1598,14 @@ U.Map = L.Map.extend({
'umap-open-browser-link flat',
container,
L._('Browse data'),
- () => this.openBrowser('expanded')
+ () => this.openBrowser('data')
)
if (this.options.facetKey) {
L.DomUtil.createButton(
'umap-open-filter-link flat',
container,
- L._('Select data'),
- this.openFacet,
- this
+ L._('Filter data'),
+ () => this.openBrowser('filters')
)
}
}
@@ -1747,17 +1742,17 @@ U.Map = L.Map.extend({
'-',
{
text: L._('See layers'),
- callback: () => this.openBrowser('condensed'),
+ callback: () => this.openBrowser('layers'),
},
{
text: L._('Browse data'),
- callback: () => this.openBrowser('expanded'),
+ callback: () => this.openBrowser('data'),
}
)
if (this.options.facetKey) {
items.push({
- text: L._('Facet search'),
- callback: this.openFacet,
+ text: L._('Filter data'),
+ callback: () => this.openBrowser('filters'),
})
}
items.push(
diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js
index fa739e35..8d4ca998 100644
--- a/umap/static/umap/js/umap.layer.js
+++ b/umap/static/umap/js/umap.layer.js
@@ -875,10 +875,7 @@ U.DataLayer = L.Evented.extend({
},
showFeature: function (feature) {
- const filterKeys = this.map.getFilterKeys(),
- filter = this.map.browser.options.filter
- if (filter && !feature.matchFilter(filter, filterKeys)) return
- if (!feature.matchFacets()) return
+ if (feature.isFiltered()) return
this.layer.addLayer(feature)
},
diff --git a/umap/static/umap/map.css b/umap/static/umap/map.css
index 666694f1..8d872b92 100644
--- a/umap/static/umap/map.css
+++ b/umap/static/umap/map.css
@@ -888,10 +888,10 @@ a.umap-control-caption,
.umap-browser .datalayer i {
cursor: pointer;
}
-.umap-browser ul {
+.umap-browser .datalayer ul {
display: none;
}
-.show-list ul {
+.umap-browser .show-list ul {
display: block;
}
@@ -982,6 +982,21 @@ a.umap-control-caption,
.umap-browser .show-list .datalayer-toggle-list {
background-position: -145px -45px;
}
+.umap-browser .filters summary {
+ background: none;
+ border: 1px solid var(--color-lightGray);
+ width: fit-content;
+ padding: 0 10px;
+ margin-bottom: var(--block-margin);
+}
+.umap-browser .filters summary {
+ list-style: none;
+ display: inline-block;
+}
+.umap-browser details[open].filters summary {
+ margin-bottom: -1px;
+ border-bottom: 1px solid var(--background-color);
+}
.datalayer-name {
cursor: pointer;
}
diff --git a/umap/static/umap/vars.css b/umap/static/umap/vars.css
index c63a1669..16d5a7f7 100644
--- a/umap/static/umap/vars.css
+++ b/umap/static/umap/vars.css
@@ -4,6 +4,13 @@
--color-darkBlue: #263B58;
--color-lightGray: #ddd;
--color-darkGray: #323737;
+ --color-light: white;
+ --color-limeGreen: #b9f5d2;
+ --color-brightCyan: #46ece6;
+ --color-lightCyan: #d4fbf9;
+
+ --background-color: var(--color-light);
+ --color-accent: var(--color-brightCyan);
/* Buttons. */
--button-primary-background: var(--color-waterMint);
@@ -20,3 +27,6 @@
--footer-height: 46px;
--control-size: 36px;
}
+.dark {
+ --background-color: var(--color-darkGray);
+}
diff --git a/umap/tests/integration/test_browser.py b/umap/tests/integration/test_browser.py
index 11a31460..90e3be9c 100644
--- a/umap/tests/integration/test_browser.py
+++ b/umap/tests/integration/test_browser.py
@@ -77,6 +77,7 @@ def test_data_browser_should_be_filterable(live_server, page, bootstrap, map):
paths = page.locator(".leaflet-overlay-pane path")
expect(markers).to_have_count(1)
expect(paths).to_have_count(2)
+ page.locator(".filters summary").click()
filter_ = page.locator("input[name='filter']")
expect(filter_).to_be_visible()
filter_.type("poly")
@@ -103,6 +104,7 @@ def test_data_browser_should_be_filterable(live_server, page, bootstrap, map):
def test_data_browser_can_show_only_visible_features(live_server, page, bootstrap, map):
# Zoom on France
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
+ page.locator(".filters summary").click()
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
@@ -114,6 +116,7 @@ def test_data_browser_can_show_only_visible_features(live_server, page, bootstra
def test_data_browser_can_mix_filter_and_bbox(live_server, page, bootstrap, map):
# Zoom on north west
page.goto(f"{live_server.url}{map.get_absolute_url()}#4/61.98/-2.68")
+ page.locator(".filters summary").click()
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
@@ -131,6 +134,7 @@ def test_data_browser_can_mix_filter_and_bbox(live_server, page, bootstrap, map)
def test_data_browser_bbox_limit_should_be_dynamic(live_server, page, bootstrap, map):
# Zoom on Europe
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
+ page.locator(".filters summary").click()
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
@@ -156,6 +160,7 @@ def test_data_browser_bbox_filter_should_be_persistent(
):
# Zoom on Europe
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
+ page.locator(".filters summary").click()
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
@@ -181,6 +186,7 @@ def test_data_browser_bbox_filtered_is_clickable(live_server, page, bootstrap, m
popup = page.locator(".leaflet-popup")
# Zoom on Europe
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
+ page.locator(".filters summary").click()
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
@@ -202,6 +208,7 @@ def test_data_browser_with_variable_in_name(live_server, page, bootstrap, map):
expect(page.get_by_text("one point in france (point)")).to_be_visible()
expect(page.get_by_text("one line in new zeland (line)")).to_be_visible()
expect(page.get_by_text("one polygon in greenland (polygon)")).to_be_visible()
+ page.locator(".filters summary").click()
filter_ = page.locator("input[name='filter']")
expect(filter_).to_be_visible()
filter_.type("foobar") # Hide all
diff --git a/umap/tests/integration/test_choropleth.py b/umap/tests/integration/test_choropleth.py
index e896dba1..65ef4ddd 100644
--- a/umap/tests/integration/test_choropleth.py
+++ b/umap/tests/integration/test_choropleth.py
@@ -50,7 +50,7 @@ def test_basic_choropleth_map_with_custom_brewer(openmap, live_server, page):
page.get_by_role("button", name="Edit").click()
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Choropleth: settings").click()
+ page.get_by_text("Choropleth: settings").click()
page.locator('select[name="brewer"]').select_option("Greens")
# Hauts-de-France
diff --git a/umap/tests/integration/test_edit_datalayer.py b/umap/tests/integration/test_edit_datalayer.py
index ecfba705..efbbdb80 100644
--- a/umap/tests/integration/test_edit_datalayer.py
+++ b/umap/tests/integration/test_edit_datalayer.py
@@ -80,7 +80,7 @@ def test_can_clone_datalayer(live_server, openmap, login, datalayer, page):
expect(markers).to_have_count(1)
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel.right").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Advanced actions").click()
+ page.get_by_text("Advanced actions").click()
page.get_by_role("button", name="Clone").click()
expect(layers).to_have_count(2)
expect(markers).to_have_count(2)
@@ -104,7 +104,7 @@ def test_can_change_icon_class(live_server, openmap, page):
page.get_by_role("link", name="Manage layers").click()
expect(page.locator(".umap-circle-icon")).to_be_hidden()
page.locator(".panel.right").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator(".umap-field-iconClass a.define").click()
page.get_by_text("Circle").click()
expect(page.locator(".umap-circle-icon")).to_be_visible()
@@ -165,14 +165,14 @@ def test_can_restore_version(live_server, openmap, page, datalayer):
marker = page.locator(".leaflet-marker-icon")
expect(marker).to_have_class(re.compile(".*umap-ball-icon.*"))
marker.click(modifiers=["Shift"])
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator("#umap-feature-shape-properties").get_by_text("Default").click()
with page.expect_response(re.compile(".*/datalayer/update/.*")):
page.get_by_role("button", name="Save").click()
expect(marker).to_have_class(re.compile(".*umap-div-icon.*"))
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel.right").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Versions").click()
+ page.get_by_text("Versions").click()
page.once("dialog", lambda dialog: dialog.accept())
page.get_by_role("button", name="Restore this version").last.click()
expect(marker).to_have_class(re.compile(".*umap-ball-icon.*"))
@@ -182,4 +182,4 @@ def test_can_edit_layer_on_ctrl_shift_click(live_server, openmap, page, datalaye
modifier = "Meta" if platform.system() == "Darwin" else "Control"
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
page.locator(".leaflet-marker-icon").click(modifiers=[modifier, "Shift"])
- expect(page.get_by_role("heading", name="Layer properties")).to_be_visible()
+ expect(page.get_by_text("Layer properties")).to_be_visible()
diff --git a/umap/tests/integration/test_edit_map.py b/umap/tests/integration/test_edit_map.py
index 122eb864..abe9d7a8 100644
--- a/umap/tests/integration/test_edit_map.py
+++ b/umap/tests/integration/test_edit_map.py
@@ -58,7 +58,7 @@ def test_zoomcontrol_impacts_ui(live_server, page, tilelayer):
expect(zoom_out).to_be_visible()
# Hide them
- page.get_by_role("heading", name="User interface options").click()
+ page.get_by_text("User interface options").click()
hide_zoom_controls = (
page.locator("div")
.filter(has_text=re.compile(r"^Display the zoom control"))
@@ -90,7 +90,7 @@ def test_map_color_impacts_data(live_server, page, tilelayer):
expect(marker_pane_p1).to_have_count(1)
# Change the default color
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator("#umap-feature-shape-properties").get_by_text("define").first.click()
page.get_by_title("Lime", exact=True).click()
@@ -108,7 +108,7 @@ def test_limitbounds_impacts_ui(live_server, page, tilelayer):
expect(gear_icon).to_be_visible()
gear_icon.click()
- page.get_by_role("heading", name="Limit bounds").click()
+ page.get_by_text("Limit bounds").click()
default_zoom_url = f"{live_server.url}/en/map/new/#5/51.110/7.053"
page.goto(default_zoom_url)
page.get_by_role("button", name="Use current bounds").click()
@@ -183,7 +183,7 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page):
# Change the default sortkey to be "key"
page.get_by_role("button", name="Edit").click()
page.get_by_role("link", name="Map advanced properties").click()
- page.get_by_role("heading", name="Default properties").click()
+ page.get_by_text("Default properties").click()
# Click "define"
page.locator(".panel .umap-field-sortKey .define").click()
diff --git a/umap/tests/integration/test_edit_marker.py b/umap/tests/integration/test_edit_marker.py
index 731de01c..99a4eaf9 100644
--- a/umap/tests/integration/test_edit_marker.py
+++ b/umap/tests/integration/test_edit_marker.py
@@ -36,7 +36,7 @@ def test_can_edit_on_shift_click(live_server, openmap, page, datalayer):
modifier = "Meta" if platform.system() == "Darwin" else "Control"
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
page.locator(".leaflet-marker-icon").click(modifiers=[modifier, "Shift"])
- expect(page.get_by_role("heading", name="Layer properties")).to_be_visible()
+ expect(page.get_by_text("Layer properties")).to_be_visible()
def test_marker_style_should_have_precedence(live_server, openmap, page, bootstrap):
@@ -45,7 +45,7 @@ def test_marker_style_should_have_precedence(live_server, openmap, page, bootstr
# Change colour at layer level
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator(".umap-field-color .define").click()
expect(page.locator(".leaflet-marker-icon .icon_container")).to_have_css(
"background-color", "rgb(0, 0, 139)"
@@ -57,7 +57,7 @@ def test_marker_style_should_have_precedence(live_server, openmap, page, bootstr
# Now change at marker level, it should take precedence
page.locator(".leaflet-marker-icon").click(modifiers=["Shift"])
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator("#umap-feature-shape-properties").get_by_text("define").first.click()
page.get_by_title("GoldenRod", exact=True).click()
expect(page.locator(".leaflet-marker-icon .icon_container")).to_have_css(
@@ -67,7 +67,7 @@ def test_marker_style_should_have_precedence(live_server, openmap, page, bootstr
# Now change again at layer level again, it should not change the marker color
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator(".umap-field-color input").click()
page.get_by_title("DarkViolet").first.click()
expect(page.locator(".leaflet-marker-icon .icon_container")).to_have_css(
@@ -87,12 +87,12 @@ def test_should_update_open_popup_on_edit(live_server, openmap, page, bootstrap)
expect(page.locator(".umap-icon-active")).to_be_hidden()
page.locator(".leaflet-marker-icon").click()
expect(page.locator(".leaflet-popup-content-wrapper")).to_be_visible()
- expect(page.get_by_role("heading", name="test marker")).to_be_visible()
+ expect(page.get_by_text("test marker")).to_be_visible()
expect(page.get_by_text("Some description")).to_be_visible()
page.get_by_role("button", name="Edit").click()
page.locator(".leaflet-marker-icon").click(modifiers=["Shift"])
page.locator('input[name="name"]').fill("test marker edited")
- expect(page.get_by_role("heading", name="test marker edited")).to_be_visible()
+ expect(page.get_by_text("test marker edited")).to_be_visible()
def test_should_follow_datalayer_style_when_changing_datalayer(
diff --git a/umap/tests/integration/test_edit_polygon.py b/umap/tests/integration/test_edit_polygon.py
index b812e181..8ae4ca26 100644
--- a/umap/tests/integration/test_edit_polygon.py
+++ b/umap/tests/integration/test_edit_polygon.py
@@ -50,7 +50,7 @@ def test_can_edit_on_shift_click(live_server, openmap, page, datalayer):
modifier = "Meta" if platform.system() == "Darwin" else "Control"
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
page.locator(".leaflet-marker-icon").click(modifiers=[modifier, "Shift"])
- expect(page.get_by_role("heading", name="Layer properties")).to_be_visible()
+ expect(page.get_by_text("Layer properties")).to_be_visible()
def test_marker_style_should_have_precedence(live_server, openmap, page, bootstrap):
@@ -59,7 +59,7 @@ def test_marker_style_should_have_precedence(live_server, openmap, page, bootstr
# Change colour at layer level
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator(".umap-field-color .define").click()
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)
page.get_by_title("DarkRed").first.click()
@@ -67,7 +67,7 @@ def test_marker_style_should_have_precedence(live_server, openmap, page, bootstr
# Now change at polygon level, it should take precedence
page.locator("path").click(modifiers=["Shift"])
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator("#umap-feature-shape-properties").get_by_text("define").first.click()
page.get_by_title("GoldenRod", exact=True).first.click()
expect(page.locator(".leaflet-overlay-pane path[fill='GoldenRod']")).to_have_count(
@@ -77,7 +77,7 @@ def test_marker_style_should_have_precedence(live_server, openmap, page, bootstr
# Now change again at layer level again, it should not change the marker color
page.get_by_role("link", name="Manage layers").click()
page.locator(".panel").get_by_title("Edit", exact=True).click()
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator(".umap-field-color input").click()
page.get_by_title("DarkViolet").first.click()
expect(page.locator(".leaflet-overlay-pane path[fill='GoldenRod']")).to_have_count(
@@ -99,7 +99,7 @@ def test_can_remove_stroke(live_server, openmap, page, bootstrap):
)
page.locator("path").click()
page.get_by_role("link", name="Toggle edit mode").click()
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator(".umap-field-stroke .define").first.click()
page.locator(".umap-field-stroke label").first.click()
expect(page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']")).to_have_count(
@@ -111,7 +111,7 @@ def test_can_remove_stroke(live_server, openmap, page, bootstrap):
def test_should_reset_style_on_cancel(live_server, openmap, page, bootstrap):
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
page.locator("path").click(modifiers=["Shift"])
- page.get_by_role("heading", name="Shape properties").click()
+ page.get_by_text("Shape properties").click()
page.locator("#umap-feature-shape-properties").get_by_text("define").first.click()
page.get_by_title("GoldenRod", exact=True).first.click()
expect(page.locator(".leaflet-overlay-pane path[fill='GoldenRod']")).to_have_count(
diff --git a/umap/tests/integration/test_facets_browser.py b/umap/tests/integration/test_facets_browser.py
index f72d3124..0ba5a621 100644
--- a/umap/tests/integration/test_facets_browser.py
+++ b/umap/tests/integration/test_facets_browser.py
@@ -93,7 +93,7 @@ DATALAYER_DATA3 = {
def test_simple_facet_search(live_server, page, map):
- map.settings["properties"]["onLoadPanel"] = "facet"
+ map.settings["properties"]["onLoadPanel"] = "datafilters"
map.settings["properties"]["facetKey"] = "mytype|My type,mynumber|My Number|number"
map.settings["properties"]["showLabel"] = True
map.save()
@@ -101,7 +101,7 @@ def test_simple_facet_search(live_server, page, map):
DataLayerFactory(map=map, data=DATALAYER_DATA2)
DataLayerFactory(map=map, data=DATALAYER_DATA3)
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
- panel = page.locator(".umap-facet-search")
+ panel = page.locator(".umap-browser")
# From a non browsable datalayer, should not be impacted
paths = page.locator(".leaflet-overlay-pane path")
expect(paths).to_be_visible()
@@ -117,17 +117,28 @@ def test_simple_facet_search(live_server, page, map):
markers = page.locator(".leaflet-marker-icon")
expect(markers).to_have_count(4)
# Tooltips
- expect(page.get_by_text("Point 1")).to_be_visible()
- expect(page.get_by_text("Point 2")).to_be_visible()
- expect(page.get_by_text("Point 3")).to_be_visible()
- expect(page.get_by_text("Point 4")).to_be_visible()
+ expect(page.get_by_role("tooltip", name="Point 1")).to_be_visible()
+ expect(page.get_by_role("tooltip", name="Point 2")).to_be_visible()
+ expect(page.get_by_role("tooltip", name="Point 3")).to_be_visible()
+ expect(page.get_by_role("tooltip", name="Point 4")).to_be_visible()
+
+ # Datalist
+ expect(panel.get_by_text("Point 1")).to_be_visible()
+ expect(panel.get_by_text("Point 2")).to_be_visible()
+ expect(panel.get_by_text("Point 3")).to_be_visible()
+ expect(panel.get_by_text("Point 4")).to_be_visible()
+
# Now let's filter
odd.click()
expect(markers).to_have_count(2)
- expect(page.get_by_text("Point 2")).to_be_hidden()
- expect(page.get_by_text("Point 4")).to_be_hidden()
- expect(page.get_by_text("Point 1")).to_be_visible()
- expect(page.get_by_text("Point 3")).to_be_visible()
+ expect(page.get_by_role("tooltip", name="Point 2")).to_be_hidden()
+ expect(page.get_by_role("tooltip", name="Point 4")).to_be_hidden()
+ expect(page.get_by_role("tooltip", name="Point 1")).to_be_visible()
+ expect(page.get_by_role("tooltip", name="Point 3")).to_be_visible()
+ expect(panel.get_by_text("Point 2")).to_be_hidden()
+ expect(panel.get_by_text("Point 4")).to_be_hidden()
+ expect(panel.get_by_text("Point 1")).to_be_visible()
+ expect(panel.get_by_text("Point 3")).to_be_visible()
expect(paths).to_be_visible
# Now let's filter
odd.click()
@@ -156,7 +167,7 @@ def test_simple_facet_search(live_server, page, map):
def test_date_facet_search(live_server, page, map):
- map.settings["properties"]["onLoadPanel"] = "facet"
+ map.settings["properties"]["onLoadPanel"] = "datafilters"
map.settings["properties"]["facetKey"] = "mydate|Date filter|date"
map.save()
DataLayerFactory(map=map, data=DATALAYER_DATA1)
@@ -174,7 +185,7 @@ def test_date_facet_search(live_server, page, map):
def test_choice_with_empty_value(live_server, page, map):
- map.settings["properties"]["onLoadPanel"] = "facet"
+ map.settings["properties"]["onLoadPanel"] = "datafilters"
map.settings["properties"]["facetKey"] = "mytype|My type"
map.save()
data = copy.deepcopy(DATALAYER_DATA1)
@@ -191,7 +202,7 @@ def test_choice_with_empty_value(live_server, page, map):
def test_number_with_zero_value(live_server, page, map):
- map.settings["properties"]["onLoadPanel"] = "facet"
+ map.settings["properties"]["onLoadPanel"] = "datafilters"
map.settings["properties"]["facetKey"] = "mynumber|Filter|number"
map.save()
data = copy.deepcopy(DATALAYER_DATA1)
@@ -205,3 +216,61 @@ def test_number_with_zero_value(live_server, page, map):
page.keyboard.press("Tab") # Move out of the input, so the "change" event is sent
markers = page.locator(".leaflet-marker-icon")
expect(markers).to_have_count(3)
+
+
+def test_facets_search_are_persistent_when_closing_panel(live_server, page, map):
+ map.settings["properties"]["onLoadPanel"] = "datafilters"
+ map.settings["properties"]["facetKey"] = "mytype|My type,mynumber|My Number|number"
+ map.save()
+ DataLayerFactory(map=map, data=DATALAYER_DATA1)
+ DataLayerFactory(map=map, data=DATALAYER_DATA2)
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/48.948/1.670")
+ panel = page.locator(".umap-browser")
+
+ # Facet values
+ odd = page.get_by_label("odd")
+ markers = page.locator(".leaflet-marker-icon")
+ expect(markers).to_have_count(4)
+
+ # Datalist in the browser
+ expect(panel.get_by_text("Point 1")).to_be_visible()
+ expect(panel.get_by_text("Point 2")).to_be_visible()
+ expect(panel.get_by_text("Point 3")).to_be_visible()
+ expect(panel.get_by_text("Point 4")).to_be_visible()
+
+ # Now let's filter
+ odd.click()
+ expect(page.locator("summary")).to_have_attribute("data-badge", " ")
+ expect(page.locator(".umap-control-browse")).to_have_attribute("data-badge", " ")
+ expect(markers).to_have_count(2)
+ expect(panel.get_by_text("Point 2")).to_be_hidden()
+ expect(panel.get_by_text("Point 4")).to_be_hidden()
+ expect(panel.get_by_text("Point 1")).to_be_visible()
+ expect(panel.get_by_text("Point 3")).to_be_visible()
+
+ # Let's filter using the number facet
+ expect(panel.get_by_label("Min")).to_have_value("10")
+ expect(panel.get_by_label("Max")).to_have_value("14")
+ page.get_by_label("Min").fill("13")
+ page.keyboard.press("Tab") # Move out of the input, so the "change" event is sent
+ expect(panel.get_by_label("Min")).to_have_attribute("data-modified", "true")
+ expect(markers).to_have_count(1)
+ expect(panel.get_by_text("Point 2")).to_be_hidden()
+ expect(panel.get_by_text("Point 4")).to_be_hidden()
+ expect(panel.get_by_text("Point 1")).to_be_hidden()
+ expect(panel.get_by_text("Point 3")).to_be_visible()
+
+ # Close panel
+ expect(panel.locator("summary")).to_have_attribute("data-badge", " ")
+ expect(page.locator(".umap-control-browse")).to_have_attribute("data-badge", " ")
+ page.get_by_role("listitem", name="Close").click()
+ page.get_by_role("button", name="See layers").click()
+ expect(panel.get_by_label("Min")).to_have_value("13")
+ expect(panel.get_by_label("Min")).to_have_attribute("data-modified", "true")
+ expect(panel.get_by_label("odd")).to_be_checked()
+
+ # Datalist in the browser should be inchanged
+ expect(panel.get_by_text("Point 2")).to_be_hidden()
+ expect(panel.get_by_text("Point 4")).to_be_hidden()
+ expect(panel.get_by_text("Point 1")).to_be_hidden()
+ expect(panel.get_by_text("Point 3")).to_be_visible()