feat: integrate facets into browser filters
This commit is contained in:
parent
8679c0ded8
commit
aa78b13f8e
11 changed files with 95 additions and 77 deletions
|
@ -9,11 +9,24 @@ export default class Browser {
|
|||
filter: '',
|
||||
inBbox: false,
|
||||
}
|
||||
this._mode = 'layers'
|
||||
}
|
||||
|
||||
set mode(value) {
|
||||
// Force only if mode is known, otherwise keep current mode.
|
||||
if (!value) return
|
||||
// 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(
|
||||
|
@ -105,6 +118,10 @@ export default class Browser {
|
|||
})
|
||||
}
|
||||
|
||||
redraw() {
|
||||
if (this.isOpen()) this.open()
|
||||
}
|
||||
|
||||
isOpen() {
|
||||
return !!document.querySelector('.umap-browser')
|
||||
}
|
||||
|
@ -126,7 +143,8 @@ export default class Browser {
|
|||
})
|
||||
}
|
||||
|
||||
open() {
|
||||
open(mode) {
|
||||
this.mode = mode
|
||||
// Get once but use it for each feature later
|
||||
this.filterKeys = this.map.getFilterKeys()
|
||||
const container = DomUtil.create('div')
|
||||
|
@ -136,17 +154,29 @@ export default class Browser {
|
|||
|
||||
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',
|
||||
})
|
||||
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(),
|
||||
})
|
||||
formContainer.appendChild(builder.build())
|
||||
if (this.map.options.facetKey) {
|
||||
fields = this.map.facets.build()
|
||||
const builder = new L.FormBuilder(this.map.facets, fields, {
|
||||
callback: () => this.onFormChange(),
|
||||
})
|
||||
formContainer.appendChild(builder.build())
|
||||
}
|
||||
|
||||
this.map.panel.open({
|
||||
content: container,
|
||||
|
@ -171,10 +201,6 @@ export default class Browser {
|
|||
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}`)
|
||||
|
|
|
@ -53,23 +53,8 @@ export default class Facets {
|
|||
return properties
|
||||
}
|
||||
|
||||
redraw() {
|
||||
if (this.isOpen()) this.open()
|
||||
}
|
||||
build() {
|
||||
|
||||
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')
|
||||
const defined = this.getDefined()
|
||||
const names = Object.keys(defined)
|
||||
const facetProperties = this.compute(names, defined)
|
||||
|
@ -114,13 +99,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() {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -660,9 +660,6 @@ const ControlsMixin = {
|
|||
'star',
|
||||
'tilelayers',
|
||||
],
|
||||
_openFacet: function () {
|
||||
this.facets.open()
|
||||
},
|
||||
|
||||
displayCaption: function () {
|
||||
const container = L.DomUtil.create('div', 'umap-caption')
|
||||
|
|
|
@ -84,6 +84,7 @@ L.DomUtil.createFieldset = (container, legend, 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.DomUtil.classIf(fieldset, 'on', options.on)
|
||||
L.DomEvent.on(legendEl, 'click', function () {
|
||||
if (L.DomUtil.hasClass(fieldset, 'on')) {
|
||||
L.DomUtil.removeClass(fieldset, 'on')
|
||||
|
|
|
@ -494,6 +494,14 @@ U.FeatureMixin = {
|
|||
this.bindTooltip(U.Utils.escapeHTML(displayName), options)
|
||||
},
|
||||
|
||||
isFiltered: function () {
|
||||
const filterKeys = this.map.getFilterKeys(),
|
||||
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++) {
|
||||
|
|
|
@ -211,15 +211,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 === 'caption') {
|
||||
this.panel.mode = 'condensed'
|
||||
this.displayCaption()
|
||||
} else if (['facet', 'datafilters'].includes(this.options.onLoadPanel)) {
|
||||
this.panel.mode = 'expanded'
|
||||
this.openFacet()
|
||||
this.openBrowser('filters')
|
||||
}
|
||||
if (L.Util.queryString('edit')) {
|
||||
if (this.hasEditMode()) this.enableEdit()
|
||||
|
@ -252,7 +252,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 +908,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)
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -1602,15 +1595,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
|
||||
() => this.openBrowser('filters')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1747,17 +1739,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(
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.get_by_role("heading", name="filters").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.get_by_role("heading", name="filters").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.get_by_role("heading", name="filters").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.get_by_role("heading", name="filters").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.get_by_role("heading", name="filters").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.get_by_role("heading", name="filters").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.get_by_role("heading", name="filters").click()
|
||||
filter_ = page.locator("input[name='filter']")
|
||||
expect(filter_).to_be_visible()
|
||||
filter_.type("foobar") # Hide all
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue