wip: another attemp to massiverly delete features from a condition

This commit is contained in:
Yohan Boniface 2024-07-04 13:12:11 +02:00
parent bf08536c30
commit ae9659620a
9 changed files with 327 additions and 69 deletions

View file

@ -27,7 +27,8 @@
.panel.full.on {
visibility: visible;
right: calc(var(--panel-gutter) * 2 + var(--control-size));
left: var(--panel-gutter);
/* left: var(--panel-gutter);*/
left: calc(var(--panel-gutter) * 3 + var(--control-size) + var(--panel-width));
height: initial;
max-height: initial;
}

View file

@ -0,0 +1,75 @@
.umap-table-editor {
width: 100%;
overflow-x: auto;
}
.umap-table-editor table {
white-space: nowrap;
table-layout: fixed;
border-collapse: collapse;
border-bottom: 1px solid black;
border-top: 1px solid black;
}
.umap-table-editor thead {
text-align: center;
height: 48px;
line-height: 48px;
background-color: #2c3133;
}
.umap-table-editor thead tr {
border-bottom: 3px solid var(--color-accent);
}
.umap-table-editor thead th {
border-left: 1px solid #0b0c0c;
}
.umap-table-editor .tbody tr input {
margin: 0;
border-right: none;
display: inline;
}
.umap-table-editor thead i {
display: none;
width: 50%;
cursor: pointer;
padding: 10px 0;
height: 24px;
line-height: 24px;
}
.umap-table-editor thead i:before {
width: 40px;
}
.umap-table-editor thead th:hover i {
display: inline-block;
}
.umap-table-editor thead th i:hover {
background-color: #33393b;
}
.umap-table-editor thead th:hover span {
display: none;
}
.umap-table-editor td {
overflow: hidden;
}
.umap-table-editor td {
border: 1px solid #222;
}
.umap-table-editor td:focus {
outline-color: var(--color-accent);
}
.umap-table-editor th, .umap-table-editor td {
padding: 10px;
vertical-align: top;
}
.umap-table-editor tr:nth-child(even) {
background-color: var(--color-mediumGray);
}
.umap-table-editor tr {
border-left: 1px solid black;
border-right: 1px solid black;
}
.umap-table-editor .formbox,
.umap-table-editor input {
margin: 0;
}
.umap-table-editor input {
border-radius: initial;
}

View file

@ -107,6 +107,7 @@ export default class Browser {
this.map.eachBrowsableDataLayer((datalayer) => {
datalayer.resetLayer(true)
this.updateDatalayer(datalayer)
if (this.map.fullPanel.isOpen()) datalayer.tableEdit()
})
this.toggleBadge()
}
@ -149,7 +150,7 @@ export default class Browser {
DomEvent.disableClickPropagation(container)
DomUtil.createTitle(container, translate('Data browser'), 'icon-layers')
const formContainer = DomUtil.createFieldset(container, L._('Filters'), {
this.formContainer = DomUtil.createFieldset(container, L._('Filters'), {
on: this.mode === 'filters',
className: 'filters',
icon: 'icon-filters',
@ -169,7 +170,7 @@ export default class Browser {
callback: () => this.onFormChange(),
})
let filtersBuilder
formContainer.appendChild(builder.build())
this.formContainer.appendChild(builder.build())
DomEvent.on(builder.form, 'reset', () => {
window.setTimeout(builder.syncAll.bind(builder))
})
@ -181,12 +182,11 @@ export default class Browser {
DomEvent.on(filtersBuilder.form, 'reset', () => {
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
})
formContainer.appendChild(filtersBuilder.build())
this.formContainer.appendChild(filtersBuilder.build())
}
const reset = DomUtil.createButton('flat', formContainer, '', () => {
builder.form.reset()
if (filtersBuilder) filtersBuilder.form.reset()
})
const reset = DomUtil.createButton('flat', this.formContainer, '', () =>
this.resetFilters()
)
DomUtil.createIcon(reset, 'icon-restore')
DomUtil.element({
tagName: 'span',
@ -202,6 +202,12 @@ export default class Browser {
this.update()
}
resetFilters() {
for (const form of this.formContainer?.querySelectorAll('form') || []) {
form.reset()
}
}
static backButton(map) {
const button = DomUtil.createButtonIcon(
DomUtil.create('li', '', undefined),

View file

@ -19,6 +19,7 @@ import Slideshow from './slideshow.js'
import { SyncEngine } from './sync/engine.js'
import Dialog from './ui/dialog.js'
import { EditPanel, FullPanel, Panel } from './ui/panel.js'
import TableEditor from './tableeditor.js'
import Tooltip from './ui/tooltip.js'
import URLs from './urls.js'
import * as Utils from './utils.js'
@ -55,6 +56,7 @@ window.U = {
Share,
Slideshow,
SyncEngine,
TableEditor,
Tooltip,
URLs,
Utils,

View file

@ -0,0 +1,232 @@
import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from './i18n.js'
export default class TableEditor {
constructor(datalayer) {
this.datalayer = datalayer
this.table = DomUtil.create('table')
this.thead = DomUtil.create('thead', '', this.table)
this.header = DomUtil.create('tr', '', this.thead)
this.body = DomUtil.create('tbody', '', this.table)
this.resetProperties()
this.body.addEventListener('dblclick', (event) => {
if (event.target.closest('[data-property]')) this.editCell(event.target)
})
this.body.addEventListener('click', (event) => this.setFocus(event.target))
this.body.addEventListener('keydown', (event) => this.onKeyDown(event))
}
renderHeaders() {
this.header.innerHTML = '<th><input type="checkbox" /></th>'
for (let i = 0; i < this.properties.length; i++) {
this.renderHeader(this.properties[i])
}
const checkbox = this.header.querySelector('input[type=checkbox]')
checkbox.addEventListener('change', (event) => {
if (checkbox.checked) this.checkAll()
else this.checkAll(false)
})
}
renderHeader(property) {
const container = DomUtil.create('th', '', this.header)
const title = DomUtil.add('span', '', container, property)
const del = DomUtil.create('i', 'umap-delete', container)
const rename = DomUtil.create('i', 'umap-edit', container)
del.title = translate('Delete this property on all the features')
rename.title = translate('Rename this property on all the features')
DomEvent.on(del, 'click', () => this.deleteProperty(property))
DomEvent.on(rename, 'click', () => this.renameProperty(property))
}
renderBody() {
const bounds = this.datalayer.map.getBounds()
const inBbox = this.datalayer.map.browser.options.inBbox
let html = ''
for (const feature of Object.values(this.datalayer._layers)) {
if (feature.isFiltered()) continue
if (inBbox && !feature.isOnScreen(bounds)) continue
html += `<tr data-feature="${feature.id}"><th><input type="checkbox" /></th>${this.properties.map((prop) => `<td tabindex="0" data-property="${prop}">${feature.properties[prop] || ''}</td>`).join('')}</tr>`
}
// this.datalayer.eachLayer(this.renderRow, this)
// const builder = new U.FormBuilder(feature, this.field_properties, {
// id: `umap-feature-properties_${L.stamp(feature)}`,
// className: 'trow',
// callback: feature.resetTooltip,
// })
// this.body.appendChild(builder.build())
this.body.innerHTML = html
}
compileProperties() {
this.resetProperties()
if (this.properties.length === 0) this.properties = ['name']
// description is a forced textarea, don't edit it in a text input, or you lose cariage returns
if (this.properties.indexOf('description') !== -1)
this.properties.splice(this.properties.indexOf('description'), 1)
this.properties.sort()
this.field_properties = []
for (let i = 0; i < this.properties.length; i++) {
this.field_properties.push([
`properties.${this.properties[i]}`,
{ wrapper: 'td' },
])
}
}
resetProperties() {
this.properties = this.datalayer._propertiesIndex
}
validateName(name) {
if (name.includes('.') !== -1) {
U.Alert.error(translate('Invalide property name: {name}', { name: name }))
return false
}
return true
}
renameProperty(property) {
this.datalayer.map.dialog
.prompt(translate('Please enter the new name of this property'))
.then(({ prompt }) => {
if (!prompt || !this.validateName(prompt)) return
this.datalayer.eachLayer((feature) => {
feature.renameProperty(property, prompt)
})
this.datalayer.deindexProperty(property)
this.datalayer.indexProperty(prompt)
this.open()
})
}
deleteProperty(property) {
this.datalayer.map.dialog
.confirm(
translate('Are you sure you want to delete this property on all the features?')
)
.then(() => {
this.datalayer.eachLayer((feature) => {
feature.deleteProperty(property)
})
this.datalayer.deindexProperty(property)
this.resetProperties()
this.open()
})
}
addProperty() {
this.datalayer.map.dialog
.prompt(translate('Please enter the name of the property'))
.then(({ prompt }) => {
if (!prompt || !this.validateName(prompt)) return
this.datalayer.indexProperty(prompt)
this.edit()
})
}
open() {
const id = 'tableeditor:edit'
this.compileProperties()
this.renderHeaders()
this.body.innerHTML = ''
this.renderBody()
const addButton = DomUtil.createButton(
'flat',
undefined,
translate('Add a new property')
)
const iconElement = DomUtil.createIcon(addButton, 'icon-add')
addButton.insertBefore(iconElement, addButton.firstChild)
DomEvent.on(addButton, 'click', this.addProperty, this)
const template = document.createElement('template')
template.innerHTML = `
<button class="flat" type="button" data-ref="delete">
<i class="icon icon-16 icon-delete"></i>${translate('Delete selected rows')}
</button>`
const deleteButton = template.content.firstElementChild
deleteButton.addEventListener('click', () => this.deleteRows())
this.datalayer.map.fullPanel.open({
content: this.table,
className: 'umap-table-editor',
actions: [addButton, deleteButton],
})
}
editCell(cell) {
const property = cell.dataset.property
const field = `properties.${property}`
const feature = this.datalayer.getFeatureById(
event.target.parentNode.dataset.feature
)
const builder = new U.FormBuilder(feature, [field], {
id: `umap-feature-properties_${L.stamp(feature)}`,
className: 'trow',
callback: feature.resetTooltip,
})
cell.innerHTML = ''
cell.appendChild(builder.build())
const input = builder.helpers[field].input
input.focus()
input.addEventListener('blur', () => {
cell.innerHTML = feature.properties[property] || ''
})
}
onKeyDown(event) {
const key = event.key
if (key === 'Enter') {
const current = this.getFocus()
if (current) {
this.editCell(current)
event.preventDefault()
event.stopPropagation()
}
}
}
checkAll(status = true) {
for (const checkbox of this.body.querySelectorAll('input[type=checkbox]')) {
checkbox.checked = status
}
}
getSelectedRows() {
return Array.from(this.body.querySelectorAll('input[type=checkbox]:checked')).map(
(checkbox) => checkbox.parentNode.parentNode
)
}
getFocus() {
return this.body.querySelector(':focus')
}
setFocus(cell) {
cell.focus({ focusVisible: true })
}
deleteRows() {
const selectedRows = this.getSelectedRows()
if (!selectedRows.length) return
this.datalayer.map.dialog
.confirm(
translate('Found {count} rows. Are you sure you want to delete all?', {
count: selectedRows.length,
})
)
.then(() => {
this.datalayer.hide()
for (const row of selectedRows) {
const id = row.dataset.feature
const feature = this.datalayer.getFeatureById(id)
feature.del()
}
this.datalayer.show()
this.datalayer.fire('datachanged')
this.renderBody()
this.datalayer.map.browser.resetFilters()
})
}
}

View file

@ -1032,7 +1032,7 @@ U.DataLayer = L.Evented.extend({
this._index.splice(this._index.indexOf(id), 1)
delete this._layers[id]
delete this.map.features_index[feature.getSlug()]
if (this.hasDataLoaded()) this.fire('datachanged')
if (this.hasDataLoaded() && this.isVisible()) this.fire('datachanged')
},
indexProperties: function (feature) {
@ -1802,7 +1802,7 @@ U.DataLayer = L.Evented.extend({
tableEdit: function () {
if (this.isRemoteLayer() || !this.isVisible()) return
const editor = new U.TableEditor(this)
editor.edit()
editor.open()
},
getFilterKeys: function () {

View file

@ -908,64 +908,6 @@ a.umap-control-caption,
padding-left: 31px;
}
/* ********************************* */
/* Table Editor */
/* ********************************* */
.umap-table-editor .table {
display: table;
width: 100%;
white-space: nowrap;
table-layout: fixed;
}
.umap-table-editor .tbody {
display: table-row-group;
}
.umap-table-editor .thead,
.umap-table-editor .trow {
display: table-row;
}
.umap-table-editor .tcell {
display: table-cell;
width: 200px;
}
.umap-table-editor .thead {
text-align: center;
height: 48px;
line-height: 48px;
background-color: #2c3133;
}
.umap-table-editor .thead .tcell {
border-left: 1px solid #0b0c0c;
}
.umap-table-editor .tbody .trow input {
margin: 0;
border-right: none;
display: inline;
}
.umap-table-editor .tbody .trow + .trow input {
border-top: none;
}
.umap-table-editor .thead i {
display: none;
width: 50%;
cursor: pointer;
padding: 10px 0;
height: 24px;
line-height: 24px;
}
.umap-table-editor .thead i:before {
width: 40px;
}
.umap-table-editor .thead .tcell:hover i {
display: inline-block;
}
.umap-table-editor .thead .tcell i:hover {
background-color: #33393b;
}
.umap-table-editor .thead .tcell:hover span {
display: none;
}
/* ********************************* */
/* Tilelayer switcher */

View file

@ -34,4 +34,5 @@
<link rel="stylesheet" href="{% static 'umap/css/tooltip.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/dialog.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/importers.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/tableeditor.css' %}" />
<link rel="stylesheet" href="{% static 'umap/theme.css' %}" />

View file

@ -51,6 +51,5 @@
<script src="{% static 'umap/js/umap.datalayer.permissions.js' %}" defer></script>
<script src="{% static 'umap/js/umap.layer.js' %}" defer></script>
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script src="{% static 'umap/js/umap.tableeditor.js' %}" defer></script>
<script src="{% static 'umap/js/umap.js' %}" defer></script>
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script>