mirror of
https://github.com/umap-project/umap.git
synced 2025-04-28 11:32:38 +02:00
fix: allow to save/undo/sync drag'n'drop of datalayers (#2677)
This also: - introduces a new DataLayerManager - removes the _umap_options coming from the geojson form the server - removes all backup related functions (now we have undo/redo) fix #2674
This commit is contained in:
commit
bffbeb5230
18 changed files with 188 additions and 175 deletions
|
@ -551,6 +551,7 @@ class DataLayer(NamedModel):
|
|||
if self.old_id:
|
||||
metadata["old_id"] = self.old_id
|
||||
metadata["id"] = self.pk
|
||||
metadata["rank"] = self.rank
|
||||
metadata["permissions"] = {"edit_status": self.edit_status}
|
||||
metadata["editMode"] = "advanced" if self.can_edit(request) else "disabled"
|
||||
metadata["_referenceVersion"] = self.reference_version
|
||||
|
|
|
@ -113,7 +113,7 @@ export default class Browser {
|
|||
}
|
||||
|
||||
onFormChange() {
|
||||
this._umap.eachBrowsableDataLayer((datalayer) => {
|
||||
this._umap.datalayers.browsable().map((datalayer) => {
|
||||
datalayer.resetLayer(true)
|
||||
this.updateDatalayer(datalayer)
|
||||
if (this._umap.fullPanel?.isOpen()) datalayer.tableEdit()
|
||||
|
@ -136,7 +136,7 @@ export default class Browser {
|
|||
onMoveEnd() {
|
||||
if (!this.isOpen()) return
|
||||
const isListDynamic = this.options.inBbox
|
||||
this._umap.eachBrowsableDataLayer((datalayer) => {
|
||||
this._umap.datalayers.browsable().map((datalayer) => {
|
||||
if (!isListDynamic && !datalayer.hasDynamicData()) return
|
||||
this.updateDatalayer(datalayer)
|
||||
})
|
||||
|
@ -145,7 +145,7 @@ export default class Browser {
|
|||
update() {
|
||||
if (!this.isOpen()) return
|
||||
this.dataContainer.innerHTML = ''
|
||||
this._umap.eachBrowsableDataLayer((datalayer) => {
|
||||
this._umap.datalayers.browsable().map((datalayer) => {
|
||||
this.addDataLayer(datalayer, this.dataContainer)
|
||||
})
|
||||
}
|
||||
|
@ -254,10 +254,10 @@ export default class Browser {
|
|||
// If at least one layer is shown, hide it
|
||||
// otherwise show all
|
||||
let allHidden = true
|
||||
this._umap.eachBrowsableDataLayer((datalayer) => {
|
||||
this._umap.datalayers.browsable().map((datalayer) => {
|
||||
if (datalayer.isVisible()) allHidden = false
|
||||
})
|
||||
this._umap.eachBrowsableDataLayer((datalayer) => {
|
||||
this._umap.datalayers.browsable().map((datalayer) => {
|
||||
datalayer._forcedVisibility = true
|
||||
if (allHidden) {
|
||||
datalayer.show()
|
||||
|
|
|
@ -68,7 +68,9 @@ export default class Caption extends Utils.WithTemplate {
|
|||
this.elements.description.hidden = true
|
||||
}
|
||||
this.elements.datalayersContainer.innerHTML = ''
|
||||
this._umap.eachDataLayerReverse((datalayer) =>
|
||||
this._umap.datalayers
|
||||
.reverse()
|
||||
.map((datalayer) =>
|
||||
this.addDataLayer(datalayer, this.elements.datalayersContainer)
|
||||
)
|
||||
this.addCredits()
|
||||
|
@ -85,7 +87,7 @@ export default class Caption extends Utils.WithTemplate {
|
|||
}
|
||||
this._umap.panel.open({ content: this.element }).then(() => {
|
||||
// Create the legend when the panel is actually on the DOM
|
||||
this._umap.eachDataLayerReverse((datalayer) => datalayer.renderLegend())
|
||||
this._umap.datalayers.reverse().map((datalayer) => datalayer.renderLegend())
|
||||
this._umap.propagate()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ export class DataLayer {
|
|||
|
||||
this.setOptions(data)
|
||||
this.pane.dataset.id = this.id
|
||||
if (this.options.rank === undefined) {
|
||||
this.options.rank = this._umap.datalayers.count()
|
||||
}
|
||||
|
||||
if (!Utils.isObject(this.options.remoteData)) {
|
||||
this.options.remoteData = {}
|
||||
|
@ -153,6 +156,9 @@ export class DataLayer {
|
|||
case 'remote-data':
|
||||
this.fetchRemoteData()
|
||||
break
|
||||
case 'datalayer-rank':
|
||||
this._umap.reorderDataLayers()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -249,16 +255,9 @@ export class DataLayer {
|
|||
if (!error) {
|
||||
this._umap.modifiedAt = response.headers.get('last-modified')
|
||||
this.setReferenceVersion({ response, sync: false })
|
||||
// FIXME: for now the _umap_options property is set dynamically from backend
|
||||
// And thus it's not in the geojson file in the server
|
||||
// So do not let all options to be reset
|
||||
// Fix is a proper migration so all datalayers settings are
|
||||
// in DB, and we remove it from geojson flat files.
|
||||
if (geojson._umap_options) {
|
||||
geojson._umap_options.editMode = this.options.editMode
|
||||
}
|
||||
delete geojson._umap_options
|
||||
// In case of maps pre 1.0 still around
|
||||
if (geojson._storage) geojson._storage.editMode = this.options.editMode
|
||||
delete geojson._storage
|
||||
await this.fromUmapGeoJSON(geojson)
|
||||
this.backupOptions()
|
||||
this._loading = false
|
||||
|
@ -287,7 +286,6 @@ export class DataLayer {
|
|||
|
||||
async fromUmapGeoJSON(geojson) {
|
||||
if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat
|
||||
geojson._umap_options.id = this.id
|
||||
if (geojson._umap_options) this.setOptions(geojson._umap_options)
|
||||
if (this.isRemoteLayer()) {
|
||||
await this.fetchRemoteData()
|
||||
|
@ -297,6 +295,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
clear() {
|
||||
// TODO do not startBatch for remoteData layer
|
||||
this.sync.startBatch()
|
||||
for (const feature of Object.values(this._features)) {
|
||||
feature.del()
|
||||
|
@ -395,12 +394,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
connectToMap() {
|
||||
if (!this._umap.datalayers[this.id]) {
|
||||
this._umap.datalayers[this.id] = this
|
||||
}
|
||||
if (!this._umap.datalayersIndex.includes(this)) {
|
||||
this._umap.datalayersIndex.push(this)
|
||||
}
|
||||
this._umap.datalayers.add(this)
|
||||
this._umap.onDataLayersChanged()
|
||||
}
|
||||
|
||||
|
@ -644,11 +638,19 @@ export class DataLayer {
|
|||
|
||||
del(sync = true) {
|
||||
const oldValue = Utils.CopyJSON(this.umapGeoJSON())
|
||||
this.erase()
|
||||
// TODO merge datalayer del and features del in same
|
||||
// batch
|
||||
this.clear()
|
||||
if (sync) {
|
||||
this.isDeleted = true
|
||||
this.sync.delete(oldValue)
|
||||
}
|
||||
this.hide()
|
||||
this.parentPane.removeChild(this.pane)
|
||||
this._umap.onDataLayersChanged()
|
||||
this.layer.onDelete(this._leafletMap)
|
||||
this.propagateDelete()
|
||||
this._leaflet_events_bk = this._leaflet_events
|
||||
}
|
||||
|
||||
empty() {
|
||||
|
@ -666,17 +668,6 @@ export class DataLayer {
|
|||
return datalayer
|
||||
}
|
||||
|
||||
erase() {
|
||||
this.hide()
|
||||
this._umap.datalayersIndex.splice(this.getRank(), 1)
|
||||
this.parentPane.removeChild(this.pane)
|
||||
this._umap.onDataLayersChanged()
|
||||
this.layer.onDelete(this._leafletMap)
|
||||
this.propagateDelete()
|
||||
this._leaflet_events_bk = this._leaflet_events
|
||||
this.clear()
|
||||
}
|
||||
|
||||
redraw() {
|
||||
if (!this.isVisible()) return
|
||||
this.eachFeature((feature) => feature.redraw())
|
||||
|
@ -1091,23 +1082,11 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
getPreviousBrowsable() {
|
||||
let id = this.getRank()
|
||||
let next
|
||||
const index = this._umap.datalayersIndex
|
||||
while (((id = index[++id] ? id : 0), (next = index[id]))) {
|
||||
if (next === this || next.canBrowse()) break
|
||||
}
|
||||
return next
|
||||
return this._umap.datalayers.prev(this)
|
||||
}
|
||||
|
||||
getNextBrowsable() {
|
||||
let id = this.getRank()
|
||||
let prev
|
||||
const index = this._umap.datalayersIndex
|
||||
while (((id = index[--id] ? id : index.length - 1), (prev = index[id]))) {
|
||||
if (prev === this || prev.canBrowse()) break
|
||||
}
|
||||
return prev
|
||||
return this._umap.datalayers.next(this)
|
||||
}
|
||||
|
||||
umapGeoJSON() {
|
||||
|
@ -1118,8 +1097,8 @@ export class DataLayer {
|
|||
}
|
||||
}
|
||||
|
||||
getRank() {
|
||||
return this._umap.datalayersIndex.indexOf(this)
|
||||
getDOMOrder() {
|
||||
return Array.from(this.parentPane.children).indexOf(this.pane)
|
||||
}
|
||||
|
||||
isReadOnly() {
|
||||
|
@ -1141,6 +1120,12 @@ export class DataLayer {
|
|||
}
|
||||
}
|
||||
|
||||
prepareOptions() {
|
||||
const options = Utils.CopyJSON(this.options)
|
||||
delete options.permissions
|
||||
return JSON.stringify(options)
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (this.isDeleted) return await this.saveDelete()
|
||||
if (!this.isRemoteLayer() && !this.isLoaded()) return
|
||||
|
@ -1148,8 +1133,8 @@ export class DataLayer {
|
|||
const formData = new FormData()
|
||||
formData.append('name', this.options.name)
|
||||
formData.append('display_on_load', !!this.options.displayOnLoad)
|
||||
formData.append('rank', this.getRank())
|
||||
formData.append('settings', JSON.stringify(this.options))
|
||||
formData.append('rank', this.options.rank)
|
||||
formData.append('settings', this.prepareOptions())
|
||||
// Filename support is shaky, don't do it for now.
|
||||
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
|
||||
formData.append('geojson', blob)
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class Facets {
|
|||
this.selected[name] = selected
|
||||
}
|
||||
|
||||
this._umap.eachBrowsableDataLayer((datalayer) => {
|
||||
this._umap.datalayers.browsable().map((datalayer) => {
|
||||
datalayer.eachFeature((feature) => {
|
||||
for (const name of names) {
|
||||
let value = feature.properties[name]
|
||||
|
|
|
@ -560,7 +560,7 @@ Fields.SlideshowDelay = class extends Fields.IntSelect {
|
|||
Fields.DataLayerSwitcher = class extends Fields.Select {
|
||||
getOptions() {
|
||||
const options = []
|
||||
this.builder._umap.eachDataLayerReverse((datalayer) => {
|
||||
this.builder._umap.datalayers.reverse().map((datalayer) => {
|
||||
if (
|
||||
datalayer.isLoaded() &&
|
||||
!datalayer.isDataReadOnly() &&
|
||||
|
|
|
@ -243,7 +243,7 @@ export default class Importer extends Utils.WithTemplate {
|
|||
this.raw = null
|
||||
const layerSelect = this.qs('[name="layer-id"]')
|
||||
layerSelect.innerHTML = ''
|
||||
this._umap.eachDataLayerReverse((datalayer) => {
|
||||
this._umap.datalayers.reverse().map((datalayer) => {
|
||||
if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
|
||||
DomUtil.element({
|
||||
tagName: 'option',
|
||||
|
|
46
umap/static/umap/js/modules/managers.js
Normal file
46
umap/static/umap/js/modules/managers.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
export class DataLayerManager extends Object {
|
||||
add(datalayer) {
|
||||
this[datalayer.id] = datalayer
|
||||
}
|
||||
active() {
|
||||
return Object.values(this)
|
||||
.filter((datalayer) => !datalayer.isDeleted)
|
||||
.sort((a, b) => a.options.rank > b.options.rank)
|
||||
}
|
||||
reverse() {
|
||||
return this.active().reverse()
|
||||
}
|
||||
count() {
|
||||
return this.active().length
|
||||
}
|
||||
find(func) {
|
||||
for (const datalayer of this.reverse()) {
|
||||
if (func.call(datalayer, datalayer)) {
|
||||
return datalayer
|
||||
}
|
||||
}
|
||||
}
|
||||
filter(func) {
|
||||
return this.active().filter(func)
|
||||
}
|
||||
visible() {
|
||||
return this.filter((datalayer) => datalayer.isVisible())
|
||||
}
|
||||
browsable() {
|
||||
return this.reverse().filter((datalayer) => datalayer.allowBrowse())
|
||||
}
|
||||
prev(datalayer) {
|
||||
const browsable = this.browsable()
|
||||
const current = browsable.indexOf(datalayer)
|
||||
const prev = browsable[current - 1] || browsable[browsable.length - 1]
|
||||
if (!prev.canBrowse()) return this.prev(prev)
|
||||
return prev
|
||||
}
|
||||
next(datalayer) {
|
||||
const browsable = this.browsable()
|
||||
const current = browsable.indexOf(datalayer)
|
||||
const next = browsable[current + 1] || browsable[0]
|
||||
if (!next.canBrowse()) return this.next(next)
|
||||
return next
|
||||
}
|
||||
}
|
|
@ -159,7 +159,7 @@ export class MapPermissions {
|
|||
`<fieldset class="separator"><legend>${translate('Datalayers')}</legend></fieldset>`
|
||||
)
|
||||
container.appendChild(fieldset)
|
||||
this._umap.eachDataLayer((datalayer) => {
|
||||
this._umap.datalayers.active().map((datalayer) => {
|
||||
datalayer.permissions.edit(fieldset)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -327,7 +327,7 @@ export const LeafletMap = BaseMap.extend({
|
|||
} else if (this.options.defaultView === 'latest') {
|
||||
this._umap.onceDataLoaded(() => {
|
||||
if (!this._umap.hasData()) return
|
||||
const datalayer = this._umap.firstVisibleDatalayer()
|
||||
const datalayer = this._umap.datalayers.visible()[0]
|
||||
let feature
|
||||
if (datalayer) {
|
||||
const feature = datalayer.getFeatureByIndex(-1)
|
||||
|
|
|
@ -10,7 +10,7 @@ import { translate } from './i18n.js'
|
|||
* - `type`: The type of the data
|
||||
* - `impacts`: A list of impacts than happen when this property is updated, among
|
||||
* 'ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data',
|
||||
* 'background' 'sync'.
|
||||
* 'background', 'sync', 'datalayer-rank'.
|
||||
*
|
||||
* - Extra keys are being passed to the FormBuilder automatically.
|
||||
*/
|
||||
|
@ -436,6 +436,10 @@ export const SCHEMA = {
|
|||
],
|
||||
default: 'Default',
|
||||
},
|
||||
rank: {
|
||||
type: Number,
|
||||
impacts: ['datalayer-rank'],
|
||||
},
|
||||
remoteData: {
|
||||
type: Object,
|
||||
impacts: ['remote-data'],
|
||||
|
|
|
@ -204,8 +204,8 @@ class IframeExporter {
|
|||
delete this.queryString.feature
|
||||
}
|
||||
if (this.options.keepCurrentDatalayers) {
|
||||
this._umap.eachDataLayer((datalayer) => {
|
||||
if (datalayer.isVisible() && datalayer.createdOnServer) {
|
||||
this._umap.datalayers.visible().map((datalayer) => {
|
||||
if (datalayer.createdOnServer) {
|
||||
datalayers.push(datalayer.id)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -66,7 +66,7 @@ export default class Slideshow extends WithTemplate {
|
|||
}
|
||||
|
||||
defaultDatalayer() {
|
||||
return this._umap.findDataLayer((d) => d.canBrowse())
|
||||
return this._umap.datalayers.find((d) => d.canBrowse())
|
||||
}
|
||||
|
||||
startSpinner() {
|
||||
|
|
|
@ -207,22 +207,35 @@ export class SyncEngine {
|
|||
this._send(operation)
|
||||
}
|
||||
|
||||
async save() {
|
||||
const needSave = new Map()
|
||||
_getDirtyObjects() {
|
||||
const dirty = new Map()
|
||||
if (!this._umap.id) {
|
||||
// There is no operation for first map save
|
||||
needSave.set(this._umap, [])
|
||||
dirty.set(this._umap, [])
|
||||
}
|
||||
const addDirtyObject = (operation) => {
|
||||
const updater = this._getUpdater(operation.subject)
|
||||
const obj = updater.getStoredObject(operation.metadata)
|
||||
if (!dirty.has(obj)) {
|
||||
dirty.set(obj, [])
|
||||
}
|
||||
dirty.get(obj).push(operation)
|
||||
}
|
||||
for (const operation of this._operations.sorted()) {
|
||||
if (operation.dirty) {
|
||||
const updater = this._getUpdater(operation.subject)
|
||||
const obj = updater.getStoredObject(operation.metadata)
|
||||
if (!needSave.has(obj)) {
|
||||
needSave.set(obj, [])
|
||||
}
|
||||
needSave.get(obj).push(operation)
|
||||
addDirtyObject(operation)
|
||||
if (operation.verb === 'batch') {
|
||||
for (const op of operation.operations) {
|
||||
addDirtyObject(op)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dirty
|
||||
}
|
||||
|
||||
async save() {
|
||||
const needSave = this._getDirtyObjects()
|
||||
for (const [obj, operations] of needSave.entries()) {
|
||||
const ok = await obj.save()
|
||||
if (!ok) return false
|
||||
|
|
|
@ -207,7 +207,7 @@ export class BottomBar extends WithTemplate {
|
|||
const select = this.elements.layers
|
||||
const selected = select.options[select.selectedIndex].value
|
||||
if (!selected) return
|
||||
this._umap.eachDataLayer((datalayer) => {
|
||||
this._umap.datalayers.active().map((datalayer) => {
|
||||
datalayer.toggle(datalayer.id === selected)
|
||||
})
|
||||
})
|
||||
|
@ -228,7 +228,7 @@ export class BottomBar extends WithTemplate {
|
|||
|
||||
buildDataLayerSwitcher() {
|
||||
this.elements.layers.innerHTML = ''
|
||||
const datalayers = this._umap.datalayersIndex.filter((d) => d.options.inCaption)
|
||||
const datalayers = this._umap.datalayers.filter((d) => d.options.inCaption)
|
||||
if (datalayers.length < 2) {
|
||||
this.elements.layers.hidden = true
|
||||
} else {
|
||||
|
|
|
@ -33,6 +33,7 @@ import { EditPanel, FullPanel, Panel } from './ui/panel.js'
|
|||
import Tooltip from './ui/tooltip.js'
|
||||
import URLs from './urls.js'
|
||||
import * as Utils from './utils.js'
|
||||
import { DataLayerManager } from './managers.js'
|
||||
|
||||
export default class Umap {
|
||||
constructor(element, geojson) {
|
||||
|
@ -166,8 +167,7 @@ export default class Umap {
|
|||
}
|
||||
|
||||
// Global storage for retrieving datalayers and features.
|
||||
this.datalayers = {} // All datalayers, including deleted.
|
||||
this.datalayersIndex = [] // Datalayers actually on the map and ordered.
|
||||
this.datalayers = new DataLayerManager()
|
||||
this.featuresIndex = {}
|
||||
|
||||
this.formatter = new Formatter(this)
|
||||
|
@ -217,7 +217,6 @@ export default class Umap {
|
|||
}
|
||||
|
||||
window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
|
||||
this.backup()
|
||||
}
|
||||
|
||||
get isDirty() {
|
||||
|
@ -616,7 +615,7 @@ export default class Umap {
|
|||
this.datalayersLoaded = true
|
||||
this.fire('datalayersloaded')
|
||||
const toLoad = []
|
||||
for (const datalayer of this.datalayersIndex) {
|
||||
for (const datalayer of this.datalayers.active()) {
|
||||
if (datalayer.showAtLoad()) toLoad.push(() => datalayer.show())
|
||||
}
|
||||
while (toLoad.length) {
|
||||
|
@ -630,7 +629,7 @@ export default class Umap {
|
|||
|
||||
createDataLayer(options = {}, sync = true) {
|
||||
options.name =
|
||||
options.name || `${translate('Layer')} ${this.datalayersIndex.length + 1}`
|
||||
options.name || `${translate('Layer')} ${this.datalayers.count() + 1}`
|
||||
const datalayer = new DataLayer(this, this._leafletMap, options)
|
||||
|
||||
if (sync !== false) {
|
||||
|
@ -651,19 +650,21 @@ export default class Umap {
|
|||
}
|
||||
|
||||
reindexDataLayers() {
|
||||
this.eachDataLayer((datalayer) => datalayer.reindex())
|
||||
this.datalayers.active().map((datalayer) => datalayer.reindex())
|
||||
this.onDataLayersChanged()
|
||||
}
|
||||
|
||||
indexDatalayers() {
|
||||
const panes = this._leafletMap.getPane('overlayPane')
|
||||
|
||||
this.datalayersIndex = []
|
||||
for (const pane of panes.children) {
|
||||
if (!pane.dataset || !pane.dataset.id) continue
|
||||
this.datalayersIndex.push(this.datalayers[pane.dataset.id])
|
||||
reorderDataLayers() {
|
||||
const parent = this._leafletMap.getPane('overlayPane')
|
||||
const datalayers = Object.values(this.datalayers)
|
||||
.filter((datalayer) => !datalayer._isDeleted)
|
||||
.sort(
|
||||
(datalayer1, datalayer2) => datalayer1.options.rank > datalayer2.options.rank
|
||||
)
|
||||
for (const datalayer of datalayers) {
|
||||
const child = parent.querySelector(`[data-id="${datalayer.id}"]`)
|
||||
parent.appendChild(child)
|
||||
}
|
||||
this.onDataLayersChanged()
|
||||
}
|
||||
|
||||
onceDatalayersLoaded(callback, context) {
|
||||
|
@ -694,7 +695,6 @@ export default class Umap {
|
|||
async saveAll() {
|
||||
if (!this.isDirty) return
|
||||
if (this._defaultExtent) this._setCenterAndZoom()
|
||||
this.backup()
|
||||
const status = await this.sync.save()
|
||||
if (!status) return
|
||||
// Do a blind render for now, as we are not sure what could
|
||||
|
@ -714,24 +714,6 @@ export default class Umap {
|
|||
return this.properties.name || translate('Untitled map')
|
||||
}
|
||||
|
||||
backup() {
|
||||
this.backupProperties()
|
||||
this._datalayersIndex_bk = [].concat(this.datalayersIndex)
|
||||
}
|
||||
|
||||
backupProperties() {
|
||||
this._backupProperties = Object.assign({}, this.properties)
|
||||
this._backupProperties.tilelayer = Object.assign({}, this.properties.tilelayer)
|
||||
this._backupProperties.limitBounds = Object.assign({}, this.properties.limitBounds)
|
||||
this._backupProperties.permissions = Object.assign({}, this.permissions.properties)
|
||||
}
|
||||
|
||||
resetProperties() {
|
||||
this.properties = Object.assign({}, this._backupProperties)
|
||||
this.properties.tilelayer = Object.assign({}, this._backupProperties.tilelayer)
|
||||
this.permissions.properties = Object.assign({}, this._backupProperties.permissions)
|
||||
}
|
||||
|
||||
setProperties(newProperties) {
|
||||
for (const key of Object.keys(SCHEMA)) {
|
||||
if (newProperties[key] !== undefined) {
|
||||
|
@ -744,24 +726,24 @@ export default class Umap {
|
|||
}
|
||||
|
||||
hasData() {
|
||||
for (const datalayer of this.datalayersIndex) {
|
||||
for (const datalayer of this.datalayers.active()) {
|
||||
if (datalayer.hasData()) return true
|
||||
}
|
||||
}
|
||||
|
||||
hasLayers() {
|
||||
return Boolean(this.datalayersIndex.length)
|
||||
return Boolean(this.datalayers.count())
|
||||
}
|
||||
|
||||
allProperties() {
|
||||
return [].concat(...this.datalayersIndex.map((dl) => dl.allProperties()))
|
||||
return [].concat(...this.datalayers.active().map((dl) => dl.allProperties()))
|
||||
}
|
||||
|
||||
sortedValues(property) {
|
||||
return []
|
||||
.concat(...this.datalayersIndex.map((dl) => dl.sortedValues(property)))
|
||||
.concat(...this.datalayers.active().map((dl) => dl.sortedValues(property)))
|
||||
.filter((val, idx, arr) => arr.indexOf(val) === idx)
|
||||
.sort(U.Utils.naturalSort)
|
||||
.sort(Utils.naturalSort)
|
||||
}
|
||||
|
||||
editCaption() {
|
||||
|
@ -1278,7 +1260,7 @@ export default class Umap {
|
|||
|
||||
toGeoJSON() {
|
||||
let features = []
|
||||
this.eachDataLayer((datalayer) => {
|
||||
this.datalayers.active().map((datalayer) => {
|
||||
if (datalayer.isVisible()) {
|
||||
features = features.concat(datalayer.featuresToGeoJSON())
|
||||
}
|
||||
|
@ -1354,13 +1336,20 @@ export default class Umap {
|
|||
if (fields.includes('properties.rules')) {
|
||||
this.rules.load()
|
||||
}
|
||||
this.eachVisibleDataLayer((datalayer) => {
|
||||
this.datalayers.visible().map((datalayer) => {
|
||||
datalayer.redraw()
|
||||
})
|
||||
break
|
||||
case 'datalayer-index':
|
||||
this.reindexDataLayers()
|
||||
break
|
||||
case 'datalayer-rank':
|
||||
// When drag'n'dropping datalayers,
|
||||
// this get called once per datalayers.
|
||||
// (and same for undo/redo of the action)
|
||||
// TODO: call only once
|
||||
this.reorderDataLayers()
|
||||
break
|
||||
case 'background':
|
||||
this._leafletMap.initTileLayers()
|
||||
break
|
||||
|
@ -1449,7 +1438,7 @@ export default class Umap {
|
|||
) {
|
||||
return datalayer
|
||||
}
|
||||
datalayer = this.findDataLayer((datalayer) => {
|
||||
datalayer = this.datalayers.find((datalayer) => {
|
||||
if (!datalayer.isDataReadOnly() && datalayer.isBrowsable()) {
|
||||
fallback = datalayer
|
||||
if (datalayer.isVisible()) return true
|
||||
|
@ -1464,49 +1453,20 @@ export default class Umap {
|
|||
return this.createDirtyDataLayer()
|
||||
}
|
||||
|
||||
findDataLayer(method, context) {
|
||||
for (let i = this.datalayersIndex.length - 1; i >= 0; i--) {
|
||||
if (method.call(context, this.datalayersIndex[i])) {
|
||||
return this.datalayersIndex[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachDataLayer(method, context) {
|
||||
for (let i = 0; i < this.datalayersIndex.length; i++) {
|
||||
method.call(context, this.datalayersIndex[i])
|
||||
}
|
||||
}
|
||||
|
||||
eachDataLayerReverse(method, context, filter) {
|
||||
for (let i = this.datalayersIndex.length - 1; i >= 0; i--) {
|
||||
if (filter && !filter.call(context, this.datalayersIndex[i])) continue
|
||||
method.call(context, this.datalayersIndex[i])
|
||||
}
|
||||
}
|
||||
|
||||
eachBrowsableDataLayer(method, context) {
|
||||
this.eachDataLayerReverse(method, context, (d) => d.allowBrowse())
|
||||
}
|
||||
|
||||
eachVisibleDataLayer(method, context) {
|
||||
this.eachDataLayerReverse(method, context, (d) => d.isVisible())
|
||||
}
|
||||
|
||||
eachFeature(callback, context) {
|
||||
this.eachBrowsableDataLayer((datalayer) => {
|
||||
if (datalayer.isVisible()) datalayer.eachFeature(callback, context)
|
||||
eachFeature(callback) {
|
||||
this.datalayers.browsable().map((datalayer) => {
|
||||
if (datalayer.isVisible()) datalayer.eachFeature(callback)
|
||||
})
|
||||
}
|
||||
|
||||
removeDataLayers() {
|
||||
this.eachDataLayerReverse((datalayer) => {
|
||||
this.datalayers.active().map((datalayer) => {
|
||||
datalayer.del()
|
||||
})
|
||||
}
|
||||
|
||||
emptyDataLayers() {
|
||||
this.eachDataLayerReverse((datalayer) => {
|
||||
this.datalayers.active().map((datalayer) => {
|
||||
datalayer.empty()
|
||||
})
|
||||
}
|
||||
|
@ -1520,7 +1480,7 @@ export default class Umap {
|
|||
</div>
|
||||
`
|
||||
const [container, { ul }] = Utils.loadTemplateWithRefs(template)
|
||||
this.eachDataLayerReverse((datalayer) => {
|
||||
this.datalayers.reverse().map((datalayer) => {
|
||||
const row = Utils.loadTemplate(
|
||||
`<li class="orderable"><i class="icon icon-16 icon-drag" title="${translate('Drag to reorder')}"></i></li>`
|
||||
)
|
||||
|
@ -1539,16 +1499,22 @@ export default class Umap {
|
|||
const onReorder = (src, dst, initialIndex, finalIndex) => {
|
||||
const movedLayer = this.datalayers[src.dataset.id]
|
||||
const targetLayer = this.datalayers[dst.dataset.id]
|
||||
const minIndex = Math.min(movedLayer.getRank(), targetLayer.getRank())
|
||||
const maxIndex = Math.max(movedLayer.getRank(), targetLayer.getRank())
|
||||
const minIndex = Math.min(movedLayer.getDOMOrder(), targetLayer.getDOMOrder())
|
||||
const maxIndex = Math.max(movedLayer.getDOMOrder(), targetLayer.getDOMOrder())
|
||||
if (finalIndex === 0) movedLayer.bringToTop()
|
||||
else if (finalIndex > initialIndex) movedLayer.insertBefore(targetLayer)
|
||||
else movedLayer.insertAfter(targetLayer)
|
||||
this.eachDataLayerReverse((datalayer) => {
|
||||
if (datalayer.getRank() >= minIndex && datalayer.getRank() <= maxIndex)
|
||||
datalayer.isDirty = true
|
||||
this.sync.startBatch()
|
||||
this.datalayers.reverse().map((datalayer) => {
|
||||
const rank = datalayer.getDOMOrder()
|
||||
if (rank >= minIndex && rank <= maxIndex) {
|
||||
const oldRank = datalayer.options.rank
|
||||
datalayer.options.rank = rank
|
||||
datalayer.sync.update('options.rank', rank, oldRank)
|
||||
}
|
||||
})
|
||||
this.indexDatalayers()
|
||||
this.sync.commitBatch()
|
||||
this.onDataLayersChanged()
|
||||
}
|
||||
const orderable = new Orderable(ul, onReorder)
|
||||
|
||||
|
@ -1570,18 +1536,6 @@ export default class Umap {
|
|||
return datalayer
|
||||
}
|
||||
|
||||
firstVisibleDatalayer() {
|
||||
return this.findDataLayer((datalayer) => {
|
||||
if (datalayer.isVisible()) return true
|
||||
})
|
||||
}
|
||||
|
||||
ensurePanesOrder() {
|
||||
this.eachDataLayer((datalayer) => {
|
||||
datalayer.bringToTop()
|
||||
})
|
||||
}
|
||||
|
||||
openBrowser(mode) {
|
||||
this.onceDatalayersLoaded(() => this.browser.open(mode))
|
||||
}
|
||||
|
@ -1732,7 +1686,7 @@ export default class Umap {
|
|||
|
||||
getLayersBounds() {
|
||||
const bounds = new latLngBounds()
|
||||
this.eachBrowsableDataLayer((d) => {
|
||||
this.datalayers.browsable().map((d) => {
|
||||
if (d.isVisible()) bounds.extend(d.layer.getBounds())
|
||||
})
|
||||
return bounds
|
||||
|
|
|
@ -131,8 +131,8 @@ class DataLayerFactory(factory.django.DjangoModelFactory):
|
|||
data.setdefault("_umap_options", {})
|
||||
if "name" in data["_umap_options"] and kwargs["name"] == cls.name:
|
||||
kwargs["name"] = data["_umap_options"]["name"]
|
||||
if "settings" not in kwargs:
|
||||
kwargs["settings"] = data.get("_umap_options", {})
|
||||
kwargs.setdefault("settings", {})
|
||||
kwargs["settings"].update(data.get("_umap_options", {}))
|
||||
else:
|
||||
data = DATALAYER_DATA.copy()
|
||||
data["_umap_options"] = {
|
||||
|
|
|
@ -50,6 +50,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
"editMode": "advanced",
|
||||
"inCaption": True,
|
||||
"id": str(datalayer.pk),
|
||||
"rank": 0,
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
# Now navigate to this map from another tab
|
||||
|
@ -87,6 +89,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"rank": 0,
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
# Now create another marker in the first tab
|
||||
|
@ -105,7 +109,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"rank": 0,
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
# And again
|
||||
|
@ -124,7 +129,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"rank": 0,
|
||||
"remoteData": {},
|
||||
}
|
||||
expect(marker_pane_p1).to_have_count(4)
|
||||
|
||||
|
@ -145,7 +151,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
|
|||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"rank": 0,
|
||||
"remoteData": {},
|
||||
}
|
||||
expect(marker_pane_p2).to_have_count(5)
|
||||
|
||||
|
@ -271,7 +278,8 @@ def test_same_second_edit_doesnt_conflict(context, live_server, tilelayer):
|
|||
"inCaption": True,
|
||||
"editMode": "advanced",
|
||||
"id": str(datalayer.pk),
|
||||
"permissions": {"edit_status": 1},
|
||||
"rank": 0,
|
||||
"remoteData": {},
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue