mirror of
https://github.com/umap-project/umap.git
synced 2025-04-28 19:42:36 +02:00
wip: another attemp to massiverly delete features from a condition
This commit is contained in:
parent
bf08536c30
commit
ae9659620a
9 changed files with 327 additions and 69 deletions
|
@ -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;
|
||||
}
|
||||
|
|
75
umap/static/umap/css/tableeditor.css
Normal file
75
umap/static/umap/css/tableeditor.css
Normal 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;
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
232
umap/static/umap/js/modules/tableeditor.js
Normal file
232
umap/static/umap/js/modules/tableeditor.js
Normal 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()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 () {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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' %}" />
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue