UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

326 lines (325 loc) 29.3 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { html as uHtml } from 'uhtml'; // SPDX-License-Identifier: Apache-2.0 import GirafeHTMLElement from '../../base/GirafeHTMLElement.js'; import LayerWms from '../../models/layers/layerwms.js'; import ColumnAliasHelper from '../../tools/utils/aliases.js'; import LayerFilterElement from './layerfilterelement.js'; import GroupLayer from '../../models/layers/grouplayer.js'; import { debounce } from '../../tools/utils/debounce.js'; import { UsedInTemplateOnly } from '../../decorators.js'; import FilterConditionElement from './filterconditionelement.js'; import LayerCsvExporter from './layercsvexporter.js'; class AdvancedFilterComponent extends GirafeHTMLElement { templateUrl = null; styleUrls = null; template = () => { return uHtml `<style> .hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}button,input,select,textarea{font:inherit}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad1);background-color:var(--bkg-color-grad2)}.gg-button:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed;border:none}.gg-button:disabled img{filter:opacity(.6)}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-icon{justify-content:center;align-items:center;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height) / 1.2);min-height:calc(var(--app-standard-height) / 1.2);max-height:calc(var(--app-standard-height) / 1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height) / 2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height) / 2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height) / 2);min-height:calc(var(--app-standard-height) / 2);max-height:calc(var(--app-standard-height) / 2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height) / 3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height) / 3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover:not(:disabled),.gg-select:hover:not(:disabled),.gg-input:hover:not(:disabled),.gg-textarea:hover:not(:disabled),.gg-icon-button:hover:not(:disabled){background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;grid-auto-flow:column;padding-bottom:1rem;font-size:1rem;display:grid}.gg-tab{border:none;border-bottom:var(--app-standard-border);cursor:pointer;color:var(--text-color);background:0 0;padding:.5rem}.gg-tab.active{border-bottom:solid 1px var(--text-color)}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height) / 2);height:calc(var(--app-standard-height) / 2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}.girafe-onboarding-theme{background-color:var(--bkg-color)!important;color:var(--text-color)!important}.girafe-onboarding-theme button{background-color:var(--bkg-color)!important;color:var(--text-color)!important;text-shadow:none!important}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}.tippy-box{background-color:var(--bkg-color)!important;border:var(--app-standard-border)!important}.tippy-content{color:var(--text-color)!important}.tippy-arrow{color:var(--bkg-color)!important}.tippy-box[data-theme~=error]{background-color:var(--error-color);white-space:pre-line}div.tippy-box[data-theme=image-popup]{border:var(--app-standard-border)!important;& div.tippy-content{background-color:var(--bkg-color)!important;& img{max-width:20rem;max-height:20rem}}& div.tippy-arrow{color:var(--bkg-color)!important}} </style><style> #content{background:var(--bkg-color);color:var(--text-color);flex-direction:column;gap:1rem;margin:0;padding:1rem;display:flex}.filter-element-container{flex-direction:column;gap:2rem;display:flex}.filter-element{background-color:color-mix(in srgb, var(--bkg-color-grad1) 30%, transparent);box-shadow:var(--bx-shdw);border-radius:3px;flex-direction:column;gap:.5rem;padding:.5rem;display:flex;position:relative}.filter-title{text-overflow:ellipsis;white-space:nowrap;padding-right:2rem;font-size:1.2rem}.filter-expression{padding-right:1.5rem;font-family:monospace,Arial,sans-serif;font-size:.85rem}.gg-label{background-color:#0000}.layer-select-container{flex-direction:column;display:flex}.text-ellipsis{text-overflow:ellipsis;overflow:hidden}.filter-condition-container{flex-direction:column;gap:.5rem;display:flex}.filter-condition{background-color:var(--bkg-color);box-shadow:var(--bx-shdw);border-radius:3px;flex-direction:column;margin-left:.5rem;padding:.5rem;display:flex}.value-inputs{gap:.5rem;display:flex}.value-inputs>input{width:100%}.btn-with-icon-and-text{color:var(--text-color);white-space:nowrap;flex-shrink:0;align-items:center;gap:.25em;width:max-content;display:inline-flex}.btn-with-icon-and-text>img{margin:auto}.apply-btn{margin-top:1rem}.export-btn{width:21px}.remove-btn{align-self:flex-end;margin:-.25rem -.25rem 0 0;font-size:1.25rem;position:absolute}ul.value-choices{border:var(--app-standard-border);max-height:10rem;margin:0;padding:.5em .25em;overflow-y:auto}ul.value-choices li{align-items:flex-start;gap:.25em;display:flex}ul.value-choices li label{flex:1}.footer-container{flex-flow:wrap;gap:.5rem;display:flex}.component-loading{margin-left:auto;margin-right:auto}.layer-loading{align-items:center;min-width:50px;margin-left:1rem;margin-right:auto;display:flex}.loader{box-sizing:border-box;border:3px solid var(--text-color);opacity:.7;vertical-align:middle;border-bottom-color:#0000;border-radius:50%;align-self:center;width:25px;height:25px;animation:1s linear infinite rotation;display:inline-block}@keyframes rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.filter-actions{flex-wrap:wrap;gap:.5rem;display:flex}#export-all-btn{margin-left:auto} </style> <style>${this.customStyle}</style> <div id="content"><div class="filter-element-container">${this.layerFilterElements.map((filterElement) => uHtml `<div class="filter-element" data-id="${filterElement.id}"><div class="filter-title text-ellipsis">${filterElement.title}</div><button class="gg-icon-button gg-small remove-btn" onclick="${() => this.onFilterRemoved(filterElement)}"><img alt="remove-icon" src="icons/close.svg"></button><div class="layer-select-container"><label class="gg-label" i18n="Layer selection" for="${'layer-select-' + filterElement.id}">Layer selection</label> <select id="${'layer-select-' + filterElement.id}" class="gg-select text-ellipsis" onchange="${(e) => this.onLayerChanged(filterElement, e)}"><option value="" i18n="${this.layers.length === 0 ? 'No layer available' : 'Select a layer'}" ?selected="${filterElement.layer === undefined}" disabled="disabled">${this.layers.length === 0 ? 'No layer available' : 'Select a layer'}</option>${this.layers.map(layer => uHtml `<option value="${layer.id}" i18n="${this.getLayerDisplayName(layer)}" ?selected="${filterElement.layer?.id === layer.id}" ?disabled="${!this.isLayerSelectable(filterElement, layer)}">${this.getLayerDisplayName(layer)}</option>`)}</select></div><div class="${filterElement.conditions ? 'filter-condition-container' : 'hidden'}">${filterElement.conditions.map((condition) => uHtml `<div class="filter-condition"><button class="gg-icon-button gg-small remove-btn" onclick="${() => this.onConditionRemoved(filterElement, condition)}"><img alt="remove-icon" src="icons/close.svg"></button> ${condition.applied ? uHtml `<div class="filter-expression">${condition.toText()}</div>` : uHtml ` ${filterElement.isLogicalOperatorSelectorVisible(condition) ? uHtml ` <label class="gg-label" i18n="Logic" for="${'attribute-select-' + condition.id}">Logic</label> <select id="${'logic-select-' + condition.id}" class="gg-select text-ellipsis" onchange="${(e) => this.onLogicalOperatorChanged(condition, e)}">${filterElement.availableLogicalOperators.map((lop) => uHtml `<option value="${lop.operator}" i18n="${lop.displayName}" ?selected="${condition.logicalOperator === lop.operator}">${lop.displayName}</option>`)}</select> ` : ''} <label class="gg-label" i18n="Attribute" for="${'attribute-select-' + condition.id}">Attribute</label> <select id="${'attribute-select-' + condition.id}" class="gg-select text-ellipsis" onchange="${(e) => this.onAttributeChanged(filterElement, condition, e)}"><option value="" i18n="Select an attribute" ?selected="${condition.attribute === undefined}" disabled="disabled">Select an attribute</option>${filterElement.attributes.map((attr) => uHtml `<option value="${attr.name}" ?selected="${condition.attribute?.name === attr.name}">${filterElement.getAttributeDisplayName(attr)}</option>`)}</select> <label class="gg-label" i18n="Operator" for="${'operator-select-' + condition.id}">Operator</label> <select id="${'operator-select-' + condition.id}" class="gg-select text-ellipsis" disabled="${!condition.isOperatorSelectorEnabled()}" onchange="${(e) => this.onOperatorChanged(condition, e)}"><option value="" i18n="Select an operator" ?selected="${condition.operator === undefined}" disabled="disabled">Select an operator</option>${(condition.availableOperators ?? []).map((op) => uHtml `<option value="${op.operator}" i18n="${op.displayName}" ?selected="${condition.operator === op.operator}" ?disabled="${!condition.isSpatialOperatorEnabled(op.operator)}">${op.displayName}</option>`)}</select> ${condition.isValueInputVisible() ? uHtml ` <label class="gg-label" i18n="Value" for="${'value-input-' + condition.id}">Value</label><div class="value-inputs"><input id="${'value-input-' + condition.id}" type="${condition.attributeInputType}" class="gg-input" disabled="${condition.operator ? '' : 'disabled'}" value="${condition.value}" oninput="${(e) => this.onValueChanged(condition, e)}"> ${condition.isValue2InputVisible() ? uHtml ` <input id="${'value-input-2-' + condition.id}" aria-label="Value 2" type="${condition.attributeInputType}" class="gg-input" value="${condition.value2}" oninput="${(e) => this.onValue2Changed(condition, e)}"> ` : ''}</div>` : ''}<div class="${condition.isGeometryToolbarVisible() ? '' : 'hidden'}"><label class="gg-label" i18n="Draw geometry" for="${'value-input-' + condition.id}">Draw geometry</label><girafe-drawing-toolbar statelocation="${FilterConditionElement.drawingStateLocation}"></girafe-drawing-toolbar></div>${condition.isChoicesListVisible() ? uHtml ` <label class="gg-label" i18n="Possible values" for="${'choice-' + condition.id}">Possible values</label><ul id="${'choice-' + condition.id}" class="value-choices">${(condition.choices ?? []).map((choice) => uHtml `<li><input id="${'choice-' + condition.id + '-' + choice}" type="checkbox" class="gg-checkbox" value="${choice}" onchange="${(e) => this.onChoiceChanged(condition, e)}"> <label for="${'choice-' + condition.id + '-' + choice}" i18n="${choice}">${choice}</label></li>`)}</ul>` : ''} <button class="gg-button gg-medium btn-with-icon-and-text apply-btn" disabled="${!condition.isValid()}" onclick="${() => this.onConditionApplied(filterElement, condition)}"><img alt="apply-icon" src="icons/checked-no-border.svg"> <span i18n="Apply">Apply</span></button> `}</div>`)}</div><div class="footer-container"><button class="${filterElement.layer ? 'gg-button gg-medium btn-with-icon-and-text' : 'hidden'}" disabled="${this.hasNonAppliedFilters()}" onclick="${() => this.onConditionAdded(filterElement)}"><img alt="plus-icon" src="icons/plus.svg"> <span i18n="Add condition">Add condition</span></button><div class="layer-loading"><span class="${filterElement.layer && this.layerIsRefreshing.get(filterElement.layer.id) ? 'loader' : 'hidden'}"></span></div>${(filterElement.conditions ?? []).length ? uHtml ` <button class="${filterElement.inverted ? 'gg-button gg-medium btn-with-icon-and-text active' : 'gg-button gg-medium btn-with-icon-and-text'}" disabled="${this.hasNonAppliedFilters()}" tip="Invert filter" onclick="${() => this.onFilterInverted(filterElement)}"><img alt="invert-icon" src="icons/invert.svg"></button> <button class="gg-button gg-medium btn-with-icon-and-text export-btn" tip="Download CSV" disabled="${this.hasNonAppliedFilters() || this.loading}" onclick="${() => this.onDownloadLayerData([filterElement])}"><img alt="download-icon" src="icons/arrow-download.svg"></button> ` : ''}</div></div>`)}</div><div class="component-loading"><span class="${this.loading ? 'loader' : 'hidden'}"></span></div><div class="filter-actions"><button class="gg-button gg-medium btn-with-icon-and-text" disabled="${this.hasNonAppliedFilters() || this.loading}" onclick="${() => this.onFilterAdded()}"><img alt="plus-icon" src="icons/plus.svg"> <span i18n="Add filter">Add filter</span></button> ${(this.layerFilterElements ?? []).length && this.layerFilterElements[0].layer ? uHtml ` <button id="export-all-btn" class="gg-button gg-medium btn-with-icon-and-text" disabled="${this.hasNonAppliedFilters() || this.loading || this.state.loading}" onclick="${() => this.onDownloadLayerData()}"><img alt="download-all-icon" src="icons/arrow-download-multiple.svg"> <span i18n="Download all to CSV">Download all to CSV</span></button> ` : ''}</div></div> ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`; }; isPanelVisible = false; panelTitle = 'advanced-filter-panel'; panelTogglePath = 'interface.advancedFilterPanelVisible'; loading = false; debouncedUpdateFilterItems = debounce(this.updateFilterItemsByLayerState.bind(this), 200); layers = []; layerFilterElements = []; columnAliasHelper; layerIsRefreshing = new Map(); csvDownloader; constructor() { super('advanced-filter'); } render() { super.render(); if (!this.isPanelVisible) { this.renderEmpty(); return; } super.girafeTranslate(); } registerEvents() { // Listen for filter changes on the layer list, triggered by changes in the simple filter component this.subscribe(/layers\.layersList\..*\.filter/, (oldFilter, newFilter, layer) => this.onFilterChangedInLayerTree(oldFilter, newFilter, layer)); // Listen for layer visibility changes this.subscribe(/layers\.layersList\..*\.activeState/, (_oldValue, _newValue, layer) => { if (layer instanceof LayerWms && this.isPanelVisible && !this.state.loading) { // Debounce the filter element updates to avoid premature action / rendering while the layer tree is still changing void this.debouncedUpdateFilterItems(); } }); this.subscribe('loading', (_, loading) => { // Update the loading state of layers to control the spinner style in the template if (!loading && this.layerIsRefreshing.size > 0) { this.layerIsRefreshing = new Map(); this.render(); } }); this.subscribe(`extendedState.${FilterConditionElement.drawingStateLocation}.features`, () => { // Render template a bit later to give the ConditionElement time to react to the state change as well // and update the geometry setTimeout(() => this.render(), 500); }); } updateFilterableLayerList() { this.layers = this.context.layerManager .getFlattenedLayerTree(this.state.layers.layersList) .filter((l) => l instanceof LayerWms && l.active && l.wfsQueryable); } /** * Updates the list of filter elements based on the current state of the layers in the layer tree. */ async updateFilterItemsByLayerState() { // Set back the currently refreshing layer this.layerIsRefreshing = new Map(); this.loading = true; this.render(); // Update which layers are active and queryable this.updateFilterableLayerList(); // Remove filter elements that are no longer needed const filtersToBeRemoved = this.layerFilterElements.filter((filterElement) => { const layerInTree = this.layers.find((l) => l.id === filterElement.layer?.id); return ( // Filter element does not have a layer yet filterElement.layer === undefined || // Layer isn't in layer tree anymore (filterElement.layer && !layerInTree) || // Filter in layer was removed by simple filter component (filterElement.layer && layerInTree.filter === undefined && filterElement.hasAppliedConditions())); }); filtersToBeRemoved.forEach((filterElement) => this.removeFilterElement(filterElement, false)); // Add filter elements for layers that are new in the layer tree and do not have a filter element yet for (const layer of this.layers) { if (layer.hasFilter && !this.layerFilterElements.some((f) => f.layer?.id === layer.id)) { await this.addFilterElementFromLayer(layer); } } // Finally, if no filter element is present, add an empty one if (this.layerFilterElements.length === 0) { this.addEmptyFilterElement(); } this.loading = false; this.render(); } /** * Determines whether the given layer is selectable based on its visibility and absence in other filter elements. */ isLayerSelectable(filterElement, layer) { return (this.layerFilterElements.filter((f) => f.id != filterElement.id && f.layer?.id === layer.id) .length === 0); } /** * Combine layer name and name of the parent group for easier identification. */ getLayerDisplayName(layer) { let nameParts = [layer.name]; if (layer.parent instanceof GroupLayer) { nameParts = [layer.parent.name, layer.name]; } const uniqueNames = new Set(nameParts.map((part) => this.context.i18nManager.getTranslation(part))); return Array.from(uniqueNames).join(' - '); } onFilterAdded() { this.addEmptyFilterElement(); this.render(); } addEmptyFilterElement() { const filterElement = new LayerFilterElement(this.context, this.columnAliasHelper, this.layerFilterElements.length); this.layerFilterElements.push(filterElement); } /** * Uses the already existing filter object in the layer to create a filter element in the UI. */ async addFilterElementFromLayer(layer) { const filterElement = new LayerFilterElement(this.context, this.columnAliasHelper, this.layerFilterElements.length); await filterElement.setFromLayer(layer); this.layerFilterElements.push(filterElement); } onFilterRemoved(filterElement) { this.removeFilterElement(filterElement, true); this.render(); } /** * Removes a filter element from the UI and optionally from the layer. * Note that the filter should only be removed from the layer if this was triggered by a user action in this component. */ removeFilterElement(filterElement, removeFilterFromLayer = true) { const idx = this.layerFilterElements.indexOf(filterElement); if (idx !== -1) { filterElement.removeConditions(); this.layerFilterElements.splice(idx, 1); if (removeFilterFromLayer && filterElement.layer?.filter) { // Note: Changing layer.filter will trigger the subscription that re-renders the component filterElement.layer.filter = undefined; } } } onFilterChangedInLayerTree(oldFilter, newFilter, layer) { if (oldFilter === undefined || newFilter === undefined) { // Cleanly add or remove filter elements in the UI void this.updateFilterItemsByLayerState(); return; } // Refresh the displayed filter element with the new filter for (const filterElement of this.layerFilterElements) { if (filterElement.layer?.id === layer.id) { filterElement.setFromLayer(layer).then(() => this.render()); break; } } } async onLayerChanged(filterElement, e) { if (!filterElement.conditions.length) { // Display an empty condition while layer metadata is being loaded filterElement.addCondition(); this.render(); } const layerId = Number(e.target.value); const layer = this.layers.find((l) => l.id === layerId); if (filterElement.hasAppliedConditions()) { // If the filter has already been applied to the layer, a new filter is created and the old one is deleted const newFilter = new LayerFilterElement(this.context, this.columnAliasHelper, this.layerFilterElements.length); this.layerFilterElements.push(newFilter); newFilter?.setLayer(layer).then((_) => this.render()); // Remove filter from previous layer and from UI this.removeFilterElement(filterElement, true); } else { // Simply switch out layers filterElement.setLayer(layer).then((_) => this.render()); } } onConditionRemoved(filterElement, condition) { filterElement.removeCondition(condition); if (condition.applied) { // Update existing filter on layer to reflect the missing condition this.applyFilter(filterElement); } this.render(); } onConditionAdded(filterElement) { filterElement.addCondition(); this.render(); } onLogicalOperatorChanged(condition, e) { const selectedLogicalOperator = e.target.value; condition.setLogicalOperator(selectedLogicalOperator); this.render(); } onAttributeChanged(filterElement, condition, e) { const selectedAttributeName = e.target.value; filterElement.setAttributeInCondition(condition, selectedAttributeName); this.render(); } onOperatorChanged(condition, e) { const selectedOperator = e.target.value; condition.setOperator(selectedOperator); this.render(); } onValueChanged(condition, e) { condition.value = e.target.value; this.render(); } onValue2Changed(condition, e) { condition.value2 = e.target.value; this.render(); } onChoiceChanged(condition, _e) { const ul = this.getById(`choice-${condition.id}`); if (ul) { const checkboxes = ul.querySelectorAll('input[type="checkbox"]:checked'); const checkedValues = Array.from(checkboxes).map((checkbox) => checkbox.value); if (checkedValues.length) { condition.value = JSON.stringify({ items: checkedValues }); } else { condition.value = undefined; } } this.render(); } onConditionApplied(filterElement, condition) { if (condition.apply()) { this.applyFilter(filterElement); } this.render(); } onFilterInverted(filterElement) { filterElement.toggleInverted(); this.applyFilter(filterElement); this.render(); } applyFilter(filterElement) { if (filterElement.layer) { this.layerIsRefreshing.set(filterElement.layer.id, true); // Note: Changing layer.filter will trigger the subscription that re-renders the component filterElement.layer.filter = filterElement.applyFilter(); } } async onDownloadLayerData(filterElements) { this.state.loading = true; filterElements ??= this.layerFilterElements; try { for (const filterElement of filterElements) { if (filterElement.hasNoneAppliedConditions()) { continue; } await this.csvDownloader.createCsvFile(filterElement.layer); if (this.csvDownloader.csvContentTruncated) { // Replace the standard wfs feature-limit warning with a more meaningful this.context.errorManager.pushMessage('wfs-max-features-reached', 'The CSV export has been truncated to the maximum number of features allowed for download', 'warning'); } } } finally { this.state.loading = false; this.render(); } } hasNonAppliedFilters() { return this.layerFilterElements.some((f) => f.hasNoneAppliedConditions() || !f.layer); } togglePanel(visible) { this.isPanelVisible = visible; if (visible) { void this.updateFilterItemsByLayerState(); } else { for (const filterElement of this.layerFilterElements) { filterElement.removeNonAppliedConditions(); } } this.render(); } connectedCallback() { super.connectedCallback(); this.columnAliasHelper = new ColumnAliasHelper(this.context.stateManager); this.csvDownloader = new LayerCsvExporter(this.context); this.updateFilterItemsByLayerState().then(() => { super.render(); super.girafeTranslate(); this.registerEvents(); }); } } __decorate([ UsedInTemplateOnly() ], AdvancedFilterComponent.prototype, "isLayerSelectable", null); __decorate([ UsedInTemplateOnly() ], AdvancedFilterComponent.prototype, "getLayerDisplayName", null); __decorate([ UsedInTemplateOnly() ], AdvancedFilterComponent.prototype, "hasNonAppliedFilters", null); export default AdvancedFilterComponent;