mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 11:52:38 +02:00
chore: internalize FormBuilder (#2420)
fix #2280 That's a first step, which: - internalize Formbuilder as a bunch of modules - use Javascript classes instead of Leaflet ones - remove dependencies to Leaflet (L.DomUtil…) - replaces `L.FormBuilder` by `Form` (in theory generic, but not quite) and `U.FormBuilder` by `MutatingForm` (knows about isDirty, `inheritable` and such) There is much more room for refactor, but let's do it step by step!
This commit is contained in:
commit
f53d435dfd
23 changed files with 1843 additions and 1824 deletions
|
@ -47,7 +47,6 @@
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-editable": "^1.3.0",
|
"leaflet-editable": "^1.3.0",
|
||||||
"leaflet-editinosm": "0.2.3",
|
"leaflet-editinosm": "0.2.3",
|
||||||
"leaflet-formbuilder": "0.2.10",
|
|
||||||
"leaflet-fullscreen": "1.0.2",
|
"leaflet-fullscreen": "1.0.2",
|
||||||
"leaflet-hash": "0.2.1",
|
"leaflet-hash": "0.2.1",
|
||||||
"leaflet-i18n": "0.3.5",
|
"leaflet-i18n": "0.3.5",
|
||||||
|
|
|
@ -17,7 +17,6 @@ mkdir -p umap/static/umap/vendors/markercluster/ && cp -r node_modules/leaflet.m
|
||||||
mkdir -p umap/static/umap/vendors/heat/ && cp -r node_modules/leaflet.heat/dist/leaflet-heat.js umap/static/umap/vendors/heat/
|
mkdir -p umap/static/umap/vendors/heat/ && cp -r node_modules/leaflet.heat/dist/leaflet-heat.js umap/static/umap/vendors/heat/
|
||||||
mkdir -p umap/static/umap/vendors/fullscreen/ && cp -r node_modules/leaflet-fullscreen/dist/** umap/static/umap/vendors/fullscreen/
|
mkdir -p umap/static/umap/vendors/fullscreen/ && cp -r node_modules/leaflet-fullscreen/dist/** umap/static/umap/vendors/fullscreen/
|
||||||
mkdir -p umap/static/umap/vendors/toolbar/ && cp -r node_modules/leaflet-toolbar/dist/leaflet.toolbar.* umap/static/umap/vendors/toolbar/
|
mkdir -p umap/static/umap/vendors/toolbar/ && cp -r node_modules/leaflet-toolbar/dist/leaflet.toolbar.* umap/static/umap/vendors/toolbar/
|
||||||
mkdir -p umap/static/umap/vendors/formbuilder/ && cp -r node_modules/leaflet-formbuilder/Leaflet.FormBuilder.js umap/static/umap/vendors/formbuilder/
|
|
||||||
mkdir -p umap/static/umap/vendors/measurable/ && cp -r node_modules/leaflet-measurable/Leaflet.Measurable.* umap/static/umap/vendors/measurable/
|
mkdir -p umap/static/umap/vendors/measurable/ && cp -r node_modules/leaflet-measurable/Leaflet.Measurable.* umap/static/umap/vendors/measurable/
|
||||||
mkdir -p umap/static/umap/vendors/photon/ && cp -r node_modules/leaflet.photon/leaflet.photon.js umap/static/umap/vendors/photon/
|
mkdir -p umap/static/umap/vendors/photon/ && cp -r node_modules/leaflet.photon/leaflet.photon.js umap/static/umap/vendors/photon/
|
||||||
mkdir -p umap/static/umap/vendors/csv2geojson/ && cp -r node_modules/csv2geojson/csv2geojson.js umap/static/umap/vendors/csv2geojson/
|
mkdir -p umap/static/umap/vendors/csv2geojson/ && cp -r node_modules/csv2geojson/csv2geojson.js umap/static/umap/vendors/csv2geojson/
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
.umap-form-inline .formbox,
|
||||||
.umap-form-inline {
|
.umap-form-inline {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
@ -381,16 +382,19 @@ input.switch:checked ~ label:after {
|
||||||
box-shadow: inset 0 0 6px 0px #2c3233;
|
box-shadow: inset 0 0 6px 0px #2c3233;
|
||||||
color: var(--color-darkGray);
|
color: var(--color-darkGray);
|
||||||
}
|
}
|
||||||
.inheritable .header,
|
.inheritable .header .buttons {
|
||||||
.inheritable {
|
padding: 0;
|
||||||
clear: both;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.inheritable .header {
|
.inheritable .header {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.inheritable .header label {
|
.inheritable .header label {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
|
width: initial;
|
||||||
}
|
}
|
||||||
.inheritable + .inheritable {
|
.inheritable + .inheritable {
|
||||||
border-top: 1px solid #222;
|
border-top: 1px solid #222;
|
||||||
|
@ -400,22 +404,11 @@ input.switch:checked ~ label:after {
|
||||||
.umap-field-iconUrl .action-button,
|
.umap-field-iconUrl .action-button,
|
||||||
.inheritable .define,
|
.inheritable .define,
|
||||||
.inheritable .undefine {
|
.inheritable .undefine {
|
||||||
float: inline-end;
|
|
||||||
width: initial;
|
width: initial;
|
||||||
min-height: 18px;
|
min-height: 18px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
.inheritable .quick-actions {
|
|
||||||
float: inline-end;
|
|
||||||
}
|
|
||||||
.inheritable .quick-actions .formbox {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.inheritable .quick-actions input {
|
|
||||||
width: 100px;
|
|
||||||
margin-inline-end: 5px;
|
|
||||||
}
|
|
||||||
.inheritable .define,
|
.inheritable .define,
|
||||||
.inheritable.undefined .undefine,
|
.inheritable.undefined .undefine,
|
||||||
.inheritable.undefined .show-on-defined {
|
.inheritable.undefined .show-on-defined {
|
||||||
|
@ -493,12 +486,15 @@ i.info {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
.flat-tabs {
|
.flat-tabs {
|
||||||
display: flex;
|
display: none;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-bottom: 1px solid #bebebe;
|
border-bottom: 1px solid #bebebe;
|
||||||
}
|
}
|
||||||
|
.flat-tabs:has(.flat) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
.flat-tabs button {
|
.flat-tabs button {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -534,7 +530,7 @@ i.info {
|
||||||
background-color: #999;
|
background-color: #999;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
display: block;
|
display: inline-block;
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@ -559,7 +555,6 @@ i.info {
|
||||||
clear: both;
|
clear: both;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
.umap-color-picker span {
|
.umap-color-picker span {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as Icon from './rendering/icon.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
import { EXPORT_FORMATS } from './formatter.js'
|
import { EXPORT_FORMATS } from './formatter.js'
|
||||||
import ContextMenu from './ui/contextmenu.js'
|
import ContextMenu from './ui/contextmenu.js'
|
||||||
|
import { Form } from './form/builder.js'
|
||||||
|
|
||||||
export default class Browser {
|
export default class Browser {
|
||||||
constructor(umap, leafletMap) {
|
constructor(umap, leafletMap) {
|
||||||
|
@ -179,9 +180,8 @@ export default class Browser {
|
||||||
],
|
],
|
||||||
['options.inBbox', { handler: 'Switch', label: translate('Current map view') }],
|
['options.inBbox', { handler: 'Switch', label: translate('Current map view') }],
|
||||||
]
|
]
|
||||||
const builder = new L.FormBuilder(this, fields, {
|
const builder = new Form(this, fields)
|
||||||
callback: () => this.onFormChange(),
|
builder.on('set', () => this.onFormChange())
|
||||||
})
|
|
||||||
let filtersBuilder
|
let filtersBuilder
|
||||||
this.formContainer.appendChild(builder.build())
|
this.formContainer.appendChild(builder.build())
|
||||||
DomEvent.on(builder.form, 'reset', () => {
|
DomEvent.on(builder.form, 'reset', () => {
|
||||||
|
@ -189,9 +189,8 @@ export default class Browser {
|
||||||
})
|
})
|
||||||
if (this._umap.properties.facetKey) {
|
if (this._umap.properties.facetKey) {
|
||||||
fields = this._umap.facets.build()
|
fields = this._umap.facets.build()
|
||||||
filtersBuilder = new L.FormBuilder(this._umap.facets, fields, {
|
filtersBuilder = new Form(this._umap.facets, fields)
|
||||||
callback: () => this.onFormChange(),
|
filtersBuilder.on('set', () => this.onFormChange())
|
||||||
})
|
|
||||||
DomEvent.on(filtersBuilder.form, 'reset', () => {
|
DomEvent.on(filtersBuilder.form, 'reset', () => {
|
||||||
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
|
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
MaskPolygon,
|
MaskPolygon,
|
||||||
} from '../rendering/ui.js'
|
} from '../rendering/ui.js'
|
||||||
import loadPopup from '../rendering/popup.js'
|
import loadPopup from '../rendering/popup.js'
|
||||||
|
import { MutatingForm } from '../form/builder.js'
|
||||||
|
|
||||||
class Feature {
|
class Feature {
|
||||||
constructor(umap, datalayer, geojson = {}, id = null) {
|
constructor(umap, datalayer, geojson = {}, id = null) {
|
||||||
|
@ -225,15 +226,11 @@ class Feature {
|
||||||
`icon-${this.getClassName()}`
|
`icon-${this.getClassName()}`
|
||||||
)
|
)
|
||||||
|
|
||||||
let builder = new U.FormBuilder(
|
let builder = new MutatingForm(this, [
|
||||||
this,
|
['datalayer', { handler: 'DataLayerSwitcher' }],
|
||||||
[['datalayer', { handler: 'DataLayerSwitcher' }]],
|
])
|
||||||
{
|
// removeLayer step will close the edit panel, let's reopen it
|
||||||
callback() {
|
builder.on('set', () => this.edit(event))
|
||||||
this.edit(event)
|
|
||||||
}, // removeLayer step will close the edit panel, let's reopen it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
container.appendChild(builder.build())
|
container.appendChild(builder.build())
|
||||||
|
|
||||||
const properties = []
|
const properties = []
|
||||||
|
@ -254,7 +251,7 @@ class Feature {
|
||||||
labelKeyFound = U.DEFAULT_LABEL_KEY
|
labelKeyFound = U.DEFAULT_LABEL_KEY
|
||||||
}
|
}
|
||||||
properties.unshift([`properties.${labelKeyFound}`, { label: labelKeyFound }])
|
properties.unshift([`properties.${labelKeyFound}`, { label: labelKeyFound }])
|
||||||
builder = new U.FormBuilder(this, properties, {
|
builder = new MutatingForm(this, properties, {
|
||||||
id: 'umap-feature-properties',
|
id: 'umap-feature-properties',
|
||||||
})
|
})
|
||||||
container.appendChild(builder.build())
|
container.appendChild(builder.build())
|
||||||
|
@ -285,7 +282,7 @@ class Feature {
|
||||||
|
|
||||||
appendEditFieldsets(container) {
|
appendEditFieldsets(container) {
|
||||||
const optionsFields = this.getShapeOptions()
|
const optionsFields = this.getShapeOptions()
|
||||||
let builder = new U.FormBuilder(this, optionsFields, {
|
let builder = new MutatingForm(this, optionsFields, {
|
||||||
id: 'umap-feature-shape-properties',
|
id: 'umap-feature-shape-properties',
|
||||||
})
|
})
|
||||||
const shapeProperties = DomUtil.createFieldset(
|
const shapeProperties = DomUtil.createFieldset(
|
||||||
|
@ -295,7 +292,7 @@ class Feature {
|
||||||
shapeProperties.appendChild(builder.build())
|
shapeProperties.appendChild(builder.build())
|
||||||
|
|
||||||
const advancedOptions = this.getAdvancedOptions()
|
const advancedOptions = this.getAdvancedOptions()
|
||||||
builder = new U.FormBuilder(this, advancedOptions, {
|
builder = new MutatingForm(this, advancedOptions, {
|
||||||
id: 'umap-feature-advanced-properties',
|
id: 'umap-feature-advanced-properties',
|
||||||
})
|
})
|
||||||
const advancedProperties = DomUtil.createFieldset(
|
const advancedProperties = DomUtil.createFieldset(
|
||||||
|
@ -305,7 +302,7 @@ class Feature {
|
||||||
advancedProperties.appendChild(builder.build())
|
advancedProperties.appendChild(builder.build())
|
||||||
|
|
||||||
const interactionOptions = this.getInteractionOptions()
|
const interactionOptions = this.getInteractionOptions()
|
||||||
builder = new U.FormBuilder(this, interactionOptions)
|
builder = new MutatingForm(this, interactionOptions)
|
||||||
const popupFieldset = DomUtil.createFieldset(
|
const popupFieldset = DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
translate('Interaction options')
|
translate('Interaction options')
|
||||||
|
@ -733,16 +730,15 @@ export class Point extends Feature {
|
||||||
['ui._latlng.lat', { handler: 'FloatInput', label: translate('Latitude') }],
|
['ui._latlng.lat', { handler: 'FloatInput', label: translate('Latitude') }],
|
||||||
['ui._latlng.lng', { handler: 'FloatInput', label: translate('Longitude') }],
|
['ui._latlng.lng', { handler: 'FloatInput', label: translate('Longitude') }],
|
||||||
]
|
]
|
||||||
const builder = new U.FormBuilder(this, coordinatesOptions, {
|
const builder = new MutatingForm(this, coordinatesOptions)
|
||||||
callback: () => {
|
builder.on('set', () => {
|
||||||
if (!this.ui._latlng.isValid()) {
|
if (!this.ui._latlng.isValid()) {
|
||||||
Alert.error(translate('Invalid latitude or longitude'))
|
Alert.error(translate('Invalid latitude or longitude'))
|
||||||
builder.restoreField('ui._latlng.lat')
|
builder.restoreField('ui._latlng.lat')
|
||||||
builder.restoreField('ui._latlng.lng')
|
builder.restoreField('ui._latlng.lng')
|
||||||
}
|
}
|
||||||
this.pullGeometry()
|
this.pullGeometry()
|
||||||
this.zoomTo({ easing: false })
|
this.zoomTo({ easing: false })
|
||||||
},
|
|
||||||
})
|
})
|
||||||
const fieldset = DomUtil.createFieldset(container, translate('Coordinates'))
|
const fieldset = DomUtil.createFieldset(container, translate('Coordinates'))
|
||||||
fieldset.appendChild(builder.build())
|
fieldset.appendChild(builder.build())
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// Uses U.FormBuilder not available as ESM
|
|
||||||
|
|
||||||
// FIXME: this module should not depend on Leaflet
|
// FIXME: this module should not depend on Leaflet
|
||||||
import {
|
import {
|
||||||
DomUtil,
|
DomUtil,
|
||||||
|
@ -22,6 +20,7 @@ import { Point, LineString, Polygon } from './features.js'
|
||||||
import TableEditor from '../tableeditor.js'
|
import TableEditor from '../tableeditor.js'
|
||||||
import { ServerStored } from '../saving.js'
|
import { ServerStored } from '../saving.js'
|
||||||
import * as Schema from '../schema.js'
|
import * as Schema from '../schema.js'
|
||||||
|
import { MutatingForm } from '../form/builder.js'
|
||||||
|
|
||||||
export const LAYER_TYPES = [
|
export const LAYER_TYPES = [
|
||||||
DefaultLayer,
|
DefaultLayer,
|
||||||
|
@ -659,7 +658,7 @@ export class DataLayer extends ServerStored {
|
||||||
{
|
{
|
||||||
label: translate('Data is browsable'),
|
label: translate('Data is browsable'),
|
||||||
handler: 'Switch',
|
handler: 'Switch',
|
||||||
helpEntries: 'browsable',
|
helpEntries: ['browsable'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -671,20 +670,19 @@ export class DataLayer extends ServerStored {
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers')
|
DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers')
|
||||||
let builder = new U.FormBuilder(this, metadataFields, {
|
let builder = new MutatingForm(this, metadataFields)
|
||||||
callback(e) {
|
builder.on('set', ({ detail }) => {
|
||||||
this._umap.onDataLayersChanged()
|
this._umap.onDataLayersChanged()
|
||||||
if (e.helper.field === 'options.type') {
|
if (detail.helper.field === 'options.type') {
|
||||||
this.edit()
|
this.edit()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
})
|
})
|
||||||
container.appendChild(builder.build())
|
container.appendChild(builder.build())
|
||||||
|
|
||||||
const layerOptions = this.layer.getEditableOptions()
|
const layerOptions = this.layer.getEditableOptions()
|
||||||
|
|
||||||
if (layerOptions.length) {
|
if (layerOptions.length) {
|
||||||
builder = new U.FormBuilder(this, layerOptions, {
|
builder = new MutatingForm(this, layerOptions, {
|
||||||
id: 'datalayer-layer-properties',
|
id: 'datalayer-layer-properties',
|
||||||
})
|
})
|
||||||
const layerProperties = DomUtil.createFieldset(
|
const layerProperties = DomUtil.createFieldset(
|
||||||
|
@ -707,7 +705,7 @@ export class DataLayer extends ServerStored {
|
||||||
'options.fillOpacity',
|
'options.fillOpacity',
|
||||||
]
|
]
|
||||||
|
|
||||||
builder = new U.FormBuilder(this, shapeOptions, {
|
builder = new MutatingForm(this, shapeOptions, {
|
||||||
id: 'datalayer-advanced-properties',
|
id: 'datalayer-advanced-properties',
|
||||||
})
|
})
|
||||||
const shapeProperties = DomUtil.createFieldset(
|
const shapeProperties = DomUtil.createFieldset(
|
||||||
|
@ -724,7 +722,7 @@ export class DataLayer extends ServerStored {
|
||||||
'options.toZoom',
|
'options.toZoom',
|
||||||
]
|
]
|
||||||
|
|
||||||
builder = new U.FormBuilder(this, optionsFields, {
|
builder = new MutatingForm(this, optionsFields, {
|
||||||
id: 'datalayer-advanced-properties',
|
id: 'datalayer-advanced-properties',
|
||||||
})
|
})
|
||||||
const advancedProperties = DomUtil.createFieldset(
|
const advancedProperties = DomUtil.createFieldset(
|
||||||
|
@ -743,7 +741,7 @@ export class DataLayer extends ServerStored {
|
||||||
'options.outlinkTarget',
|
'options.outlinkTarget',
|
||||||
'options.interactive',
|
'options.interactive',
|
||||||
]
|
]
|
||||||
builder = new U.FormBuilder(this, popupFields)
|
builder = new MutatingForm(this, popupFields)
|
||||||
const popupFieldset = DomUtil.createFieldset(
|
const popupFieldset = DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
translate('Interaction options')
|
translate('Interaction options')
|
||||||
|
@ -799,7 +797,7 @@ export class DataLayer extends ServerStored {
|
||||||
container,
|
container,
|
||||||
translate('Remote data')
|
translate('Remote data')
|
||||||
)
|
)
|
||||||
builder = new U.FormBuilder(this, remoteDataFields)
|
builder = new MutatingForm(this, remoteDataFields)
|
||||||
remoteDataContainer.appendChild(builder.build())
|
remoteDataContainer.appendChild(builder.build())
|
||||||
DomUtil.createButton(
|
DomUtil.createButton(
|
||||||
'button umap-verify',
|
'button umap-verify',
|
||||||
|
|
241
umap/static/umap/js/modules/form/builder.js
Normal file
241
umap/static/umap/js/modules/form/builder.js
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
import getClass from './fields.js'
|
||||||
|
import * as Utils from '../utils.js'
|
||||||
|
import { SCHEMA } from '../schema.js'
|
||||||
|
import { translate } from '../i18n.js'
|
||||||
|
|
||||||
|
export class Form extends Utils.WithEvents {
|
||||||
|
constructor(obj, fields, properties) {
|
||||||
|
super()
|
||||||
|
this.setProperties(properties)
|
||||||
|
this.defaultProperties = {}
|
||||||
|
this.obj = obj
|
||||||
|
this.form = Utils.loadTemplate('<form></form>')
|
||||||
|
this.setFields(fields)
|
||||||
|
if (this.properties.id) {
|
||||||
|
this.form.id = this.properties.id
|
||||||
|
}
|
||||||
|
if (this.properties.className) {
|
||||||
|
this.form.classList.add(...this.properties.className.split(' '))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setProperties(properties) {
|
||||||
|
this.properties = Object.assign({}, this.properties, properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFields(fields) {
|
||||||
|
this.fields = fields || []
|
||||||
|
this.helpers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
this.form.innerHTML = ''
|
||||||
|
for (const definition of this.fields) {
|
||||||
|
this.buildField(this.makeField(definition))
|
||||||
|
}
|
||||||
|
return this.form
|
||||||
|
}
|
||||||
|
|
||||||
|
buildField(field) {
|
||||||
|
field.buildTemplate()
|
||||||
|
field.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
makeField(field) {
|
||||||
|
// field can be either a string like "option.name" or a full definition array,
|
||||||
|
// like ['properties.tilelayer.tms', {handler: 'CheckBox', helpText: 'TMS format'}]
|
||||||
|
let properties
|
||||||
|
if (Array.isArray(field)) {
|
||||||
|
properties = field[1] || {}
|
||||||
|
field = field[0]
|
||||||
|
} else {
|
||||||
|
properties = this.defaultProperties[this.getName(field)] || {}
|
||||||
|
}
|
||||||
|
const class_ = getClass(properties.handler || 'Input')
|
||||||
|
this.helpers[field] = new class_(this, field, properties)
|
||||||
|
return this.helpers[field]
|
||||||
|
}
|
||||||
|
|
||||||
|
getter(field) {
|
||||||
|
const path = field.split('.')
|
||||||
|
let value = this.obj
|
||||||
|
for (const sub of path) {
|
||||||
|
try {
|
||||||
|
value = value[sub]
|
||||||
|
} catch {
|
||||||
|
console.log(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
setter(field, value) {
|
||||||
|
const path = field.split('.')
|
||||||
|
let obj = this.obj
|
||||||
|
let what
|
||||||
|
for (let i = 0, l = path.length; i < l; i++) {
|
||||||
|
what = path[i]
|
||||||
|
if (what === path[l - 1]) {
|
||||||
|
if (typeof value === 'undefined') {
|
||||||
|
delete obj[what]
|
||||||
|
} else {
|
||||||
|
obj[what] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj = obj[what]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreField(field) {
|
||||||
|
const initial = this.helpers[field].initial
|
||||||
|
this.setter(field, initial)
|
||||||
|
}
|
||||||
|
|
||||||
|
getName(field) {
|
||||||
|
const fieldEls = field.split('.')
|
||||||
|
return fieldEls[fieldEls.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAll() {
|
||||||
|
for (const helper of Object.values(this.helpers)) {
|
||||||
|
helper.fetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncAll() {
|
||||||
|
for (const helper of Object.values(this.helpers)) {
|
||||||
|
helper.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostSync(helper) {
|
||||||
|
if (this.properties.callback) {
|
||||||
|
this.properties.callback(helper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {}
|
||||||
|
|
||||||
|
getTemplate(helper) {
|
||||||
|
return `
|
||||||
|
<div class="formbox" data-ref=container>
|
||||||
|
${helper.getTemplate()}
|
||||||
|
<small class="help-text" data-ref=helpText></small>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MutatingForm extends Form {
|
||||||
|
constructor(obj, fields, properties) {
|
||||||
|
super(obj, fields, properties)
|
||||||
|
this._umap = obj._umap || properties.umap
|
||||||
|
this.computeDefaultProperties()
|
||||||
|
// this.on('finish', this.finish)
|
||||||
|
}
|
||||||
|
|
||||||
|
computeDefaultProperties() {
|
||||||
|
const customHandlers = {
|
||||||
|
sortKey: 'PropertyInput',
|
||||||
|
easing: 'Switch',
|
||||||
|
facetKey: 'PropertyInput',
|
||||||
|
slugKey: 'PropertyInput',
|
||||||
|
labelKey: 'PropertyInput',
|
||||||
|
}
|
||||||
|
for (const [key, schema] of Object.entries(SCHEMA)) {
|
||||||
|
if (schema.type === Boolean) {
|
||||||
|
if (schema.nullable) schema.handler = 'NullableChoices'
|
||||||
|
else schema.handler = 'Switch'
|
||||||
|
} else if (schema.type === 'Text') {
|
||||||
|
schema.handler = 'Textarea'
|
||||||
|
} else if (schema.type === Number) {
|
||||||
|
if (schema.step) schema.handler = 'Range'
|
||||||
|
else schema.handler = 'IntInput'
|
||||||
|
} else if (schema.choices) {
|
||||||
|
const text_length = schema.choices.reduce(
|
||||||
|
(acc, [_, label]) => acc + label.length,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
// Try to be smart and use MultiChoice only
|
||||||
|
// for choices where labels are shorts…
|
||||||
|
if (text_length < 40) {
|
||||||
|
schema.handler = 'MultiChoice'
|
||||||
|
} else {
|
||||||
|
schema.handler = 'Select'
|
||||||
|
schema.selectOptions = schema.choices
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (key) {
|
||||||
|
case 'color':
|
||||||
|
case 'fillColor':
|
||||||
|
schema.handler = 'ColorPicker'
|
||||||
|
break
|
||||||
|
case 'iconUrl':
|
||||||
|
schema.handler = 'IconUrl'
|
||||||
|
break
|
||||||
|
case 'licence':
|
||||||
|
schema.handler = 'LicenceChooser'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customHandlers[key]) {
|
||||||
|
schema.handler = customHandlers[key]
|
||||||
|
}
|
||||||
|
// Input uses this key for its type attribute
|
||||||
|
delete schema.type
|
||||||
|
this.defaultProperties[key] = schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setter(field, value) {
|
||||||
|
super.setter(field, value)
|
||||||
|
this.obj.isDirty = true
|
||||||
|
if ('render' in this.obj) {
|
||||||
|
this.obj.render([field], this)
|
||||||
|
}
|
||||||
|
if ('sync' in this.obj) {
|
||||||
|
this.obj.sync.update(field, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplate(helper) {
|
||||||
|
let template
|
||||||
|
if (helper.properties.inheritable) {
|
||||||
|
const extraClassName = helper.get(true) === undefined ? ' undefined' : ''
|
||||||
|
template = `
|
||||||
|
<div class="umap-field-${helper.name} formbox inheritable${extraClassName}">
|
||||||
|
<div class="header" data-ref=header>
|
||||||
|
${helper.getLabelTemplate()}
|
||||||
|
<span class="actions show-on-defined" data-ref=actions></span>
|
||||||
|
<span class="buttons" data-ref=buttons>
|
||||||
|
<button type="button" class="button undefine" data-ref=undefine>${translate('clear')}</button>
|
||||||
|
<button type="button" class="button define" data-ref=define>${translate('define')}</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="show-on-defined" data-ref=container>
|
||||||
|
${helper.getTemplate()}
|
||||||
|
<small class="help-text" data-ref=helpText></small>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
} else {
|
||||||
|
template = `
|
||||||
|
<div class="formbox umap-field-${helper.name}" data-ref=container>
|
||||||
|
${helper.getLabelTemplate()}
|
||||||
|
${helper.getTemplate()}
|
||||||
|
<small class="help-text" data-ref=helpText></small>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
return template
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
super.build()
|
||||||
|
this._umap.help.parse(this.form)
|
||||||
|
return this.form
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(helper) {
|
||||||
|
helper.input?.blur()
|
||||||
|
}
|
||||||
|
}
|
1330
umap/static/umap/js/modules/form/fields.js
Normal file
1330
umap/static/umap/js/modules/form/fields.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -228,7 +228,9 @@ export default class Help {
|
||||||
|
|
||||||
parse(container) {
|
parse(container) {
|
||||||
for (const element of container.querySelectorAll('[data-help]')) {
|
for (const element of container.querySelectorAll('[data-help]')) {
|
||||||
this.button(element, element.dataset.help.split(','))
|
if (element.dataset.help) {
|
||||||
|
this.button(element, element.dataset.help.split(','))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { translate } from './i18n.js'
|
||||||
import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
import { uMapAlert as Alert } from '../components/alerts/alert.js'
|
||||||
import { ServerStored } from './saving.js'
|
import { ServerStored } from './saving.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
|
import { MutatingForm } from './form/builder.js'
|
||||||
|
|
||||||
// Dedicated object so we can deal with a separate dirty status, and thus
|
// Dedicated object so we can deal with a separate dirty status, and thus
|
||||||
// call the endpoint only when needed, saving one call at each save.
|
// call the endpoint only when needed, saving one call at each save.
|
||||||
|
@ -58,7 +59,7 @@ export class MapPermissions extends ServerStored {
|
||||||
selectOptions: this._umap.properties.share_statuses,
|
selectOptions: this._umap.properties.share_statuses,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
const builder = new U.FormBuilder(this, fields)
|
const builder = new MutatingForm(this, fields)
|
||||||
const form = builder.build()
|
const form = builder.build()
|
||||||
container.appendChild(form)
|
container.appendChild(form)
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ export class MapPermissions extends ServerStored {
|
||||||
{ handler: 'ManageEditors', label: translate("Map's editors") },
|
{ handler: 'ManageEditors', label: translate("Map's editors") },
|
||||||
])
|
])
|
||||||
|
|
||||||
const builder = new U.FormBuilder(this, topFields)
|
const builder = new MutatingForm(this, topFields)
|
||||||
const form = builder.build()
|
const form = builder.build()
|
||||||
container.appendChild(form)
|
container.appendChild(form)
|
||||||
if (collaboratorsFields.length) {
|
if (collaboratorsFields.length) {
|
||||||
|
@ -141,7 +142,7 @@ export class MapPermissions extends ServerStored {
|
||||||
`<fieldset class="separator"><legend>${translate('Manage collaborators')}</legend></fieldset>`
|
`<fieldset class="separator"><legend>${translate('Manage collaborators')}</legend></fieldset>`
|
||||||
)
|
)
|
||||||
container.appendChild(fieldset)
|
container.appendChild(fieldset)
|
||||||
const builder = new U.FormBuilder(this, collaboratorsFields)
|
const builder = new MutatingForm(this, collaboratorsFields)
|
||||||
const form = builder.build()
|
const form = builder.build()
|
||||||
container.appendChild(form)
|
container.appendChild(form)
|
||||||
}
|
}
|
||||||
|
@ -269,7 +270,7 @@ export class DataLayerPermissions extends ServerStored {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
const builder = new U.FormBuilder(this, fields, {
|
const builder = new MutatingForm(this, fields, {
|
||||||
className: 'umap-form datalayer-permissions',
|
className: 'umap-form datalayer-permissions',
|
||||||
})
|
})
|
||||||
const form = builder.build()
|
const form = builder.build()
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { translate } from './i18n.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
import { AutocompleteDatalist } from './autocomplete.js'
|
import { AutocompleteDatalist } from './autocomplete.js'
|
||||||
import Orderable from './orderable.js'
|
import Orderable from './orderable.js'
|
||||||
|
import { MutatingForm } from './form/builder.js'
|
||||||
|
|
||||||
const EMPTY_VALUES = ['', undefined, null]
|
const EMPTY_VALUES = ['', undefined, null]
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ class Rule {
|
||||||
'options.dashArray',
|
'options.dashArray',
|
||||||
]
|
]
|
||||||
const container = DomUtil.create('div')
|
const container = DomUtil.create('div')
|
||||||
const builder = new U.FormBuilder(this, options)
|
const builder = new MutatingForm(this, options)
|
||||||
const defaultShapeProperties = DomUtil.add('div', '', container)
|
const defaultShapeProperties = DomUtil.add('div', '', container)
|
||||||
defaultShapeProperties.appendChild(builder.build())
|
defaultShapeProperties.appendChild(builder.build())
|
||||||
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
|
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
import { EXPORT_FORMATS } from './formatter.js'
|
import { EXPORT_FORMATS } from './formatter.js'
|
||||||
import { translate } from './i18n.js'
|
import { translate } from './i18n.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
|
import { MutatingForm } from './form/builder.js'
|
||||||
|
|
||||||
export default class Share {
|
export default class Share {
|
||||||
constructor(umap) {
|
constructor(umap) {
|
||||||
|
@ -125,9 +126,8 @@ export default class Share {
|
||||||
exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
|
exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
|
||||||
}
|
}
|
||||||
buildIframeCode()
|
buildIframeCode()
|
||||||
const builder = new U.FormBuilder(iframeExporter, UIFields, {
|
const builder = new MutatingForm(iframeExporter, UIFields)
|
||||||
callback: buildIframeCode,
|
builder.on('set', buildIframeCode)
|
||||||
})
|
|
||||||
const iframeOptions = DomUtil.createFieldset(
|
const iframeOptions = DomUtil.createFieldset(
|
||||||
this.container,
|
this.container,
|
||||||
translate('Embed and link options')
|
translate('Embed and link options')
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
import { translate } from './i18n.js'
|
import { translate } from './i18n.js'
|
||||||
import ContextMenu from './ui/contextmenu.js'
|
import ContextMenu from './ui/contextmenu.js'
|
||||||
import { WithTemplate, loadTemplate } from './utils.js'
|
import { WithTemplate, loadTemplate } from './utils.js'
|
||||||
|
import { MutatingForm } from './form/builder.js'
|
||||||
|
|
||||||
const TEMPLATE = `
|
const TEMPLATE = `
|
||||||
<table>
|
<table>
|
||||||
|
@ -205,7 +206,7 @@ export default class TableEditor extends WithTemplate {
|
||||||
const tr = event.target.closest('tr')
|
const tr = event.target.closest('tr')
|
||||||
const feature = this.datalayer.getFeatureById(tr.dataset.feature)
|
const feature = this.datalayer.getFeatureById(tr.dataset.feature)
|
||||||
const handler = property === 'description' ? 'Textarea' : 'Input'
|
const handler = property === 'description' ? 'Textarea' : 'Input'
|
||||||
const builder = new U.FormBuilder(feature, [[field, { handler }]], {
|
const builder = new MutatingForm(feature, [[field, { handler }]], {
|
||||||
id: `umap-feature-properties_${L.stamp(feature)}`,
|
id: `umap-feature-properties_${L.stamp(feature)}`,
|
||||||
})
|
})
|
||||||
cell.innerHTML = ''
|
cell.innerHTML = ''
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
uMapAlert as Alert,
|
uMapAlert as Alert,
|
||||||
} from '../components/alerts/alert.js'
|
} from '../components/alerts/alert.js'
|
||||||
import Orderable from './orderable.js'
|
import Orderable from './orderable.js'
|
||||||
|
import { MutatingForm } from './form/builder.js'
|
||||||
|
|
||||||
export default class Umap extends ServerStored {
|
export default class Umap extends ServerStored {
|
||||||
constructor(element, geojson) {
|
constructor(element, geojson) {
|
||||||
|
@ -734,7 +735,7 @@ export default class Umap extends ServerStored {
|
||||||
const metadataFields = ['properties.name', 'properties.description']
|
const metadataFields = ['properties.name', 'properties.description']
|
||||||
|
|
||||||
DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption')
|
DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption')
|
||||||
const builder = new U.FormBuilder(this, metadataFields, {
|
const builder = new MutatingForm(this, metadataFields, {
|
||||||
className: 'map-metadata',
|
className: 'map-metadata',
|
||||||
umap: this,
|
umap: this,
|
||||||
})
|
})
|
||||||
|
@ -749,7 +750,7 @@ export default class Umap extends ServerStored {
|
||||||
'properties.permanentCredit',
|
'properties.permanentCredit',
|
||||||
'properties.permanentCreditBackground',
|
'properties.permanentCreditBackground',
|
||||||
]
|
]
|
||||||
const creditsBuilder = new U.FormBuilder(this, creditsFields, { umap: this })
|
const creditsBuilder = new MutatingForm(this, creditsFields, { umap: this })
|
||||||
credits.appendChild(creditsBuilder.build())
|
credits.appendChild(creditsBuilder.build())
|
||||||
this.editPanel.open({ content: container })
|
this.editPanel.open({ content: container })
|
||||||
}
|
}
|
||||||
|
@ -770,7 +771,7 @@ export default class Umap extends ServerStored {
|
||||||
'properties.captionBar',
|
'properties.captionBar',
|
||||||
'properties.captionMenus',
|
'properties.captionMenus',
|
||||||
])
|
])
|
||||||
const builder = new U.FormBuilder(this, UIFields, { umap: this })
|
const builder = new MutatingForm(this, UIFields, { umap: this })
|
||||||
const controlsOptions = DomUtil.createFieldset(
|
const controlsOptions = DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
translate('User interface options')
|
translate('User interface options')
|
||||||
|
@ -793,7 +794,7 @@ export default class Umap extends ServerStored {
|
||||||
'properties.dashArray',
|
'properties.dashArray',
|
||||||
]
|
]
|
||||||
|
|
||||||
const builder = new U.FormBuilder(this, shapeOptions, { umap: this })
|
const builder = new MutatingForm(this, shapeOptions, { umap: this })
|
||||||
const defaultShapeProperties = DomUtil.createFieldset(
|
const defaultShapeProperties = DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
translate('Default shape properties')
|
translate('Default shape properties')
|
||||||
|
@ -812,7 +813,7 @@ export default class Umap extends ServerStored {
|
||||||
'properties.slugKey',
|
'properties.slugKey',
|
||||||
]
|
]
|
||||||
|
|
||||||
const builder = new U.FormBuilder(this, optionsFields, { umap: this })
|
const builder = new MutatingForm(this, optionsFields, { umap: this })
|
||||||
const defaultProperties = DomUtil.createFieldset(
|
const defaultProperties = DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
translate('Default properties')
|
translate('Default properties')
|
||||||
|
@ -830,7 +831,7 @@ export default class Umap extends ServerStored {
|
||||||
'properties.labelInteractive',
|
'properties.labelInteractive',
|
||||||
'properties.outlinkTarget',
|
'properties.outlinkTarget',
|
||||||
]
|
]
|
||||||
const builder = new U.FormBuilder(this, popupFields, { umap: this })
|
const builder = new MutatingForm(this, popupFields, { umap: this })
|
||||||
const popupFieldset = DomUtil.createFieldset(
|
const popupFieldset = DomUtil.createFieldset(
|
||||||
container,
|
container,
|
||||||
translate('Default interaction options')
|
translate('Default interaction options')
|
||||||
|
@ -887,7 +888,7 @@ export default class Umap extends ServerStored {
|
||||||
container,
|
container,
|
||||||
translate('Custom background')
|
translate('Custom background')
|
||||||
)
|
)
|
||||||
const builder = new U.FormBuilder(this, tilelayerFields, { umap: this })
|
const builder = new MutatingForm(this, tilelayerFields, { umap: this })
|
||||||
customTilelayer.appendChild(builder.build())
|
customTilelayer.appendChild(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -935,7 +936,7 @@ export default class Umap extends ServerStored {
|
||||||
['properties.overlay.tms', { handler: 'Switch', label: translate('TMS format') }],
|
['properties.overlay.tms', { handler: 'Switch', label: translate('TMS format') }],
|
||||||
]
|
]
|
||||||
const overlay = DomUtil.createFieldset(container, translate('Custom overlay'))
|
const overlay = DomUtil.createFieldset(container, translate('Custom overlay'))
|
||||||
const builder = new U.FormBuilder(this, overlayFields, { umap: this })
|
const builder = new MutatingForm(this, overlayFields, { umap: this })
|
||||||
overlay.appendChild(builder.build())
|
overlay.appendChild(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,7 +963,7 @@ export default class Umap extends ServerStored {
|
||||||
{ handler: 'BlurFloatInput', placeholder: translate('max East') },
|
{ handler: 'BlurFloatInput', placeholder: translate('max East') },
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
const boundsBuilder = new U.FormBuilder(this, boundsFields, { umap: this })
|
const boundsBuilder = new MutatingForm(this, boundsFields, { umap: this })
|
||||||
limitBounds.appendChild(boundsBuilder.build())
|
limitBounds.appendChild(boundsBuilder.build())
|
||||||
const boundsButtons = DomUtil.create('div', 'button-bar half', limitBounds)
|
const boundsButtons = DomUtil.create('div', 'button-bar half', limitBounds)
|
||||||
DomUtil.createButton(
|
DomUtil.createButton(
|
||||||
|
@ -1027,14 +1028,7 @@ export default class Umap extends ServerStored {
|
||||||
{ handler: 'Switch', label: translate('Autostart when map is loaded') },
|
{ handler: 'Switch', label: translate('Autostart when map is loaded') },
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
const slideshowBuilder = new U.FormBuilder(this, slideshowFields, {
|
const slideshowBuilder = new MutatingForm(this, slideshowFields, {
|
||||||
callback: () => {
|
|
||||||
this.slideshow.load()
|
|
||||||
// FIXME when we refactor formbuilder: this callback is called in a 'postsync'
|
|
||||||
// event, which comes after the call of `setter` method, which will call the
|
|
||||||
// map.render method, which should do this redraw.
|
|
||||||
this.bottomBar.redraw()
|
|
||||||
},
|
|
||||||
umap: this,
|
umap: this,
|
||||||
})
|
})
|
||||||
slideshow.appendChild(slideshowBuilder.build())
|
slideshow.appendChild(slideshowBuilder.build())
|
||||||
|
@ -1042,7 +1036,9 @@ export default class Umap extends ServerStored {
|
||||||
|
|
||||||
_editSync(container) {
|
_editSync(container) {
|
||||||
const sync = DomUtil.createFieldset(container, translate('Real-time collaboration'))
|
const sync = DomUtil.createFieldset(container, translate('Real-time collaboration'))
|
||||||
const builder = new U.FormBuilder(this, ['properties.syncEnabled'], { umap: this })
|
const builder = new MutatingForm(this, ['properties.syncEnabled'], {
|
||||||
|
umap: this,
|
||||||
|
})
|
||||||
sync.appendChild(builder.build())
|
sync.appendChild(builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1348,6 +1344,10 @@ export default class Umap extends ServerStored {
|
||||||
}
|
}
|
||||||
this.topBar.redraw()
|
this.topBar.redraw()
|
||||||
},
|
},
|
||||||
|
'properties.slideshow.active': () => {
|
||||||
|
this.slideshow.load()
|
||||||
|
this.bottomBar.redraw()
|
||||||
|
},
|
||||||
numberOfConnectedPeers: () => {
|
numberOfConnectedPeers: () => {
|
||||||
Utils.eachElement('.connected-peers span', (el) => {
|
Utils.eachElement('.connected-peers span', (el) => {
|
||||||
if (this.sync.websocketConnected) {
|
if (this.sync.websocketConnected) {
|
||||||
|
@ -1459,7 +1459,7 @@ export default class Umap extends ServerStored {
|
||||||
const row = DomUtil.create('li', 'orderable', ul)
|
const row = DomUtil.create('li', 'orderable', ul)
|
||||||
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
|
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
|
||||||
datalayer.renderToolbox(row)
|
datalayer.renderToolbox(row)
|
||||||
const builder = new U.FormBuilder(
|
const builder = new MutatingForm(
|
||||||
datalayer,
|
datalayer,
|
||||||
[['options.name', { handler: 'EditableText' }]],
|
[['options.name', { handler: 'EditableText' }]],
|
||||||
{ className: 'umap-form-inline' }
|
{ className: 'umap-form-inline' }
|
||||||
|
|
|
@ -416,9 +416,11 @@ export function loadTemplate(html) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadTemplateWithRefs(html) {
|
export function loadTemplateWithRefs(html) {
|
||||||
const element = loadTemplate(html)
|
const template = document.createElement('template')
|
||||||
|
template.innerHTML = html
|
||||||
|
const element = template.content.firstElementChild
|
||||||
const elements = {}
|
const elements = {}
|
||||||
for (const node of element.querySelectorAll('[data-ref]')) {
|
for (const node of template.content.querySelectorAll('[data-ref]')) {
|
||||||
elements[node.dataset.ref] = node
|
elements[node.dataset.ref] = node
|
||||||
}
|
}
|
||||||
return [element, elements]
|
return [element, elements]
|
||||||
|
@ -446,3 +448,169 @@ export function eachElement(selector, callback) {
|
||||||
callback(el)
|
callback(el)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WithEvents {
|
||||||
|
constructor() {
|
||||||
|
this._target = new EventTarget()
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventType, callback) {
|
||||||
|
if (typeof callback !== 'function') return
|
||||||
|
this._target.addEventListener(eventType, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fire(eventType, detail) {
|
||||||
|
const event = new CustomEvent(eventType, { detail })
|
||||||
|
this._target.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const COLORS = [
|
||||||
|
'Black',
|
||||||
|
'Navy',
|
||||||
|
'DarkBlue',
|
||||||
|
'MediumBlue',
|
||||||
|
'Blue',
|
||||||
|
'DarkGreen',
|
||||||
|
'Green',
|
||||||
|
'Teal',
|
||||||
|
'DarkCyan',
|
||||||
|
'DeepSkyBlue',
|
||||||
|
'DarkTurquoise',
|
||||||
|
'MediumSpringGreen',
|
||||||
|
'Lime',
|
||||||
|
'SpringGreen',
|
||||||
|
'Aqua',
|
||||||
|
'Cyan',
|
||||||
|
'MidnightBlue',
|
||||||
|
'DodgerBlue',
|
||||||
|
'LightSeaGreen',
|
||||||
|
'ForestGreen',
|
||||||
|
'SeaGreen',
|
||||||
|
'DarkSlateGray',
|
||||||
|
'DarkSlateGrey',
|
||||||
|
'LimeGreen',
|
||||||
|
'MediumSeaGreen',
|
||||||
|
'Turquoise',
|
||||||
|
'RoyalBlue',
|
||||||
|
'SteelBlue',
|
||||||
|
'DarkSlateBlue',
|
||||||
|
'MediumTurquoise',
|
||||||
|
'Indigo',
|
||||||
|
'DarkOliveGreen',
|
||||||
|
'CadetBlue',
|
||||||
|
'CornflowerBlue',
|
||||||
|
'MediumAquaMarine',
|
||||||
|
'DimGray',
|
||||||
|
'DimGrey',
|
||||||
|
'SlateBlue',
|
||||||
|
'OliveDrab',
|
||||||
|
'SlateGray',
|
||||||
|
'SlateGrey',
|
||||||
|
'LightSlateGray',
|
||||||
|
'LightSlateGrey',
|
||||||
|
'MediumSlateBlue',
|
||||||
|
'LawnGreen',
|
||||||
|
'Chartreuse',
|
||||||
|
'Aquamarine',
|
||||||
|
'Maroon',
|
||||||
|
'Purple',
|
||||||
|
'Olive',
|
||||||
|
'Gray',
|
||||||
|
'Grey',
|
||||||
|
'SkyBlue',
|
||||||
|
'LightSkyBlue',
|
||||||
|
'BlueViolet',
|
||||||
|
'DarkRed',
|
||||||
|
'DarkMagenta',
|
||||||
|
'SaddleBrown',
|
||||||
|
'DarkSeaGreen',
|
||||||
|
'LightGreen',
|
||||||
|
'MediumPurple',
|
||||||
|
'DarkViolet',
|
||||||
|
'PaleGreen',
|
||||||
|
'DarkOrchid',
|
||||||
|
'YellowGreen',
|
||||||
|
'Sienna',
|
||||||
|
'Brown',
|
||||||
|
'DarkGray',
|
||||||
|
'DarkGrey',
|
||||||
|
'LightBlue',
|
||||||
|
'GreenYellow',
|
||||||
|
'PaleTurquoise',
|
||||||
|
'LightSteelBlue',
|
||||||
|
'PowderBlue',
|
||||||
|
'FireBrick',
|
||||||
|
'DarkGoldenRod',
|
||||||
|
'MediumOrchid',
|
||||||
|
'RosyBrown',
|
||||||
|
'DarkKhaki',
|
||||||
|
'Silver',
|
||||||
|
'MediumVioletRed',
|
||||||
|
'IndianRed',
|
||||||
|
'Peru',
|
||||||
|
'Chocolate',
|
||||||
|
'Tan',
|
||||||
|
'LightGray',
|
||||||
|
'LightGrey',
|
||||||
|
'Thistle',
|
||||||
|
'Orchid',
|
||||||
|
'GoldenRod',
|
||||||
|
'PaleVioletRed',
|
||||||
|
'Crimson',
|
||||||
|
'Gainsboro',
|
||||||
|
'Plum',
|
||||||
|
'BurlyWood',
|
||||||
|
'LightCyan',
|
||||||
|
'Lavender',
|
||||||
|
'DarkSalmon',
|
||||||
|
'Violet',
|
||||||
|
'PaleGoldenRod',
|
||||||
|
'LightCoral',
|
||||||
|
'Khaki',
|
||||||
|
'AliceBlue',
|
||||||
|
'HoneyDew',
|
||||||
|
'Azure',
|
||||||
|
'SandyBrown',
|
||||||
|
'Wheat',
|
||||||
|
'Beige',
|
||||||
|
'WhiteSmoke',
|
||||||
|
'MintCream',
|
||||||
|
'GhostWhite',
|
||||||
|
'Salmon',
|
||||||
|
'AntiqueWhite',
|
||||||
|
'Linen',
|
||||||
|
'LightGoldenRodYellow',
|
||||||
|
'OldLace',
|
||||||
|
'Red',
|
||||||
|
'Fuchsia',
|
||||||
|
'Magenta',
|
||||||
|
'DeepPink',
|
||||||
|
'OrangeRed',
|
||||||
|
'Tomato',
|
||||||
|
'HotPink',
|
||||||
|
'Coral',
|
||||||
|
'DarkOrange',
|
||||||
|
'LightSalmon',
|
||||||
|
'Orange',
|
||||||
|
'LightPink',
|
||||||
|
'Pink',
|
||||||
|
'Gold',
|
||||||
|
'PeachPuff',
|
||||||
|
'NavajoWhite',
|
||||||
|
'Moccasin',
|
||||||
|
'Bisque',
|
||||||
|
'MistyRose',
|
||||||
|
'BlanchedAlmond',
|
||||||
|
'PapayaWhip',
|
||||||
|
'LavenderBlush',
|
||||||
|
'SeaShell',
|
||||||
|
'Cornsilk',
|
||||||
|
'LemonChiffon',
|
||||||
|
'FloralWhite',
|
||||||
|
'Snow',
|
||||||
|
'Yellow',
|
||||||
|
'LightYellow',
|
||||||
|
'Ivory',
|
||||||
|
'White',
|
||||||
|
]
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,468 +0,0 @@
|
||||||
L.FormBuilder = L.Evented.extend({
|
|
||||||
options: {
|
|
||||||
className: 'leaflet-form',
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultOptions: {
|
|
||||||
// Eg.:
|
|
||||||
// name: {label: L._('name')},
|
|
||||||
// description: {label: L._('description'), handler: 'Textarea'},
|
|
||||||
// opacity: {label: L._('opacity'), helpText: L._('Opacity, from 0.1 to 1.0 (opaque).')},
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function (obj, fields, options) {
|
|
||||||
L.setOptions(this, options)
|
|
||||||
this.obj = obj
|
|
||||||
this.form = L.DomUtil.create('form', this.options.className)
|
|
||||||
this.setFields(fields)
|
|
||||||
if (this.options.id) {
|
|
||||||
this.form.id = this.options.id
|
|
||||||
}
|
|
||||||
if (this.options.className) {
|
|
||||||
L.DomUtil.addClass(this.form, this.options.className)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setFields: function (fields) {
|
|
||||||
this.fields = fields || []
|
|
||||||
this.helpers = {}
|
|
||||||
},
|
|
||||||
|
|
||||||
build: function () {
|
|
||||||
this.form.innerHTML = ''
|
|
||||||
for (const idx in this.fields) {
|
|
||||||
this.buildField(this.fields[idx])
|
|
||||||
}
|
|
||||||
this.on('postsync', this.onPostSync)
|
|
||||||
return this.form
|
|
||||||
},
|
|
||||||
|
|
||||||
buildField: function (field) {
|
|
||||||
// field can be either a string like "option.name" or a full definition array,
|
|
||||||
// like ['options.tilelayer.tms', {handler: 'CheckBox', helpText: 'TMS format'}]
|
|
||||||
let type
|
|
||||||
let helper
|
|
||||||
let options
|
|
||||||
if (Array.isArray(field)) {
|
|
||||||
options = field[1] || {}
|
|
||||||
field = field[0]
|
|
||||||
} else {
|
|
||||||
options = this.defaultOptions[this.getName(field)] || {}
|
|
||||||
}
|
|
||||||
type = options.handler || 'Input'
|
|
||||||
if (typeof type === 'string' && L.FormBuilder[type]) {
|
|
||||||
helper = new L.FormBuilder[type](this, field, options)
|
|
||||||
} else {
|
|
||||||
helper = new type(this, field, options)
|
|
||||||
}
|
|
||||||
this.helpers[field] = helper
|
|
||||||
return helper
|
|
||||||
},
|
|
||||||
|
|
||||||
getter: function (field) {
|
|
||||||
const path = field.split('.')
|
|
||||||
let value = this.obj
|
|
||||||
for (const sub of path) {
|
|
||||||
try {
|
|
||||||
value = value[sub]
|
|
||||||
} catch {
|
|
||||||
console.log(field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
|
||||||
|
|
||||||
setter: function (field, value) {
|
|
||||||
const path = field.split('.')
|
|
||||||
let obj = this.obj
|
|
||||||
let what
|
|
||||||
for (let i = 0, l = path.length; i < l; i++) {
|
|
||||||
what = path[i]
|
|
||||||
if (what === path[l - 1]) {
|
|
||||||
if (typeof value === 'undefined') {
|
|
||||||
delete obj[what]
|
|
||||||
} else {
|
|
||||||
obj[what] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
obj = obj[what]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
restoreField: function (field) {
|
|
||||||
const initial = this.helpers[field].initial
|
|
||||||
this.setter(field, initial)
|
|
||||||
},
|
|
||||||
|
|
||||||
getName: (field) => {
|
|
||||||
const fieldEls = field.split('.')
|
|
||||||
return fieldEls[fieldEls.length - 1]
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchAll: function () {
|
|
||||||
for (const helper of Object.values(this.helpers)) {
|
|
||||||
helper.fetch()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
syncAll: function () {
|
|
||||||
for (const helper of Object.values(this.helpers)) {
|
|
||||||
helper.sync()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onPostSync: function (e) {
|
|
||||||
if (e.helper.options.callback) {
|
|
||||||
e.helper.options.callback.call(e.helper.options.callbackContext || this.obj, e)
|
|
||||||
}
|
|
||||||
if (this.options.callback) {
|
|
||||||
this.options.callback.call(this.options.callbackContext || this.obj, e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.Element = L.Evented.extend({
|
|
||||||
initialize: function (builder, field, options) {
|
|
||||||
this.builder = builder
|
|
||||||
this.obj = this.builder.obj
|
|
||||||
this.form = this.builder.form
|
|
||||||
this.field = field
|
|
||||||
L.setOptions(this, options)
|
|
||||||
this.fieldEls = this.field.split('.')
|
|
||||||
this.name = this.builder.getName(field)
|
|
||||||
this.parentNode = this.getParentNode()
|
|
||||||
this.buildLabel()
|
|
||||||
this.build()
|
|
||||||
this.buildHelpText()
|
|
||||||
this.fireAndForward('helper:init')
|
|
||||||
},
|
|
||||||
|
|
||||||
fireAndForward: function (type, e = {}) {
|
|
||||||
e.helper = this
|
|
||||||
this.fire(type, e)
|
|
||||||
this.builder.fire(type, e)
|
|
||||||
if (this.obj.fire) this.obj.fire(type, e)
|
|
||||||
},
|
|
||||||
|
|
||||||
getParentNode: function () {
|
|
||||||
return this.options.wrapper
|
|
||||||
? L.DomUtil.create(
|
|
||||||
this.options.wrapper,
|
|
||||||
this.options.wrapperClass || '',
|
|
||||||
this.form
|
|
||||||
)
|
|
||||||
: this.form
|
|
||||||
},
|
|
||||||
|
|
||||||
get: function () {
|
|
||||||
return this.builder.getter(this.field)
|
|
||||||
},
|
|
||||||
|
|
||||||
toHTML: function () {
|
|
||||||
return this.get()
|
|
||||||
},
|
|
||||||
|
|
||||||
toJS: function () {
|
|
||||||
return this.value()
|
|
||||||
},
|
|
||||||
|
|
||||||
sync: function () {
|
|
||||||
this.fireAndForward('presync')
|
|
||||||
this.set()
|
|
||||||
this.fireAndForward('postsync')
|
|
||||||
},
|
|
||||||
|
|
||||||
set: function () {
|
|
||||||
this.builder.setter(this.field, this.toJS())
|
|
||||||
},
|
|
||||||
|
|
||||||
getLabelParent: function () {
|
|
||||||
return this.parentNode
|
|
||||||
},
|
|
||||||
|
|
||||||
getHelpTextParent: function () {
|
|
||||||
return this.parentNode
|
|
||||||
},
|
|
||||||
|
|
||||||
buildLabel: function () {
|
|
||||||
if (this.options.label) {
|
|
||||||
this.label = L.DomUtil.create('label', '', this.getLabelParent())
|
|
||||||
this.label.innerHTML = this.options.label
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
buildHelpText: function () {
|
|
||||||
if (this.options.helpText) {
|
|
||||||
const container = L.DomUtil.create('small', 'help-text', this.getHelpTextParent())
|
|
||||||
container.innerHTML = this.options.helpText
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: () => {},
|
|
||||||
|
|
||||||
finish: function () {
|
|
||||||
this.fireAndForward('finish')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.Textarea = L.FormBuilder.Element.extend({
|
|
||||||
build: function () {
|
|
||||||
this.input = L.DomUtil.create(
|
|
||||||
'textarea',
|
|
||||||
this.options.className || '',
|
|
||||||
this.parentNode
|
|
||||||
)
|
|
||||||
if (this.options.placeholder) this.input.placeholder = this.options.placeholder
|
|
||||||
this.fetch()
|
|
||||||
L.DomEvent.on(this.input, 'input', this.sync, this)
|
|
||||||
L.DomEvent.on(this.input, 'keypress', this.onKeyPress, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: function () {
|
|
||||||
const value = this.toHTML()
|
|
||||||
this.initial = value
|
|
||||||
if (value) {
|
|
||||||
this.input.value = value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
value: function () {
|
|
||||||
return this.input.value
|
|
||||||
},
|
|
||||||
|
|
||||||
onKeyPress: function (e) {
|
|
||||||
if (e.key === 'Enter' && (e.shiftKey || e.ctrlKey)) {
|
|
||||||
L.DomEvent.stop(e)
|
|
||||||
this.finish()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.Input = L.FormBuilder.Element.extend({
|
|
||||||
build: function () {
|
|
||||||
this.input = L.DomUtil.create(
|
|
||||||
'input',
|
|
||||||
this.options.className || '',
|
|
||||||
this.parentNode
|
|
||||||
)
|
|
||||||
this.input.type = this.type()
|
|
||||||
this.input.name = this.name
|
|
||||||
this.input._helper = this
|
|
||||||
if (this.options.placeholder) {
|
|
||||||
this.input.placeholder = this.options.placeholder
|
|
||||||
}
|
|
||||||
if (this.options.min !== undefined) {
|
|
||||||
this.input.min = this.options.min
|
|
||||||
}
|
|
||||||
if (this.options.max !== undefined) {
|
|
||||||
this.input.max = this.options.max
|
|
||||||
}
|
|
||||||
if (this.options.step) {
|
|
||||||
this.input.step = this.options.step
|
|
||||||
}
|
|
||||||
this.fetch()
|
|
||||||
L.DomEvent.on(this.input, this.getSyncEvent(), this.sync, this)
|
|
||||||
L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: function () {
|
|
||||||
const value = this.toHTML() !== undefined ? this.toHTML() : null
|
|
||||||
this.initial = value
|
|
||||||
this.input.value = value
|
|
||||||
},
|
|
||||||
|
|
||||||
getSyncEvent: () => 'input',
|
|
||||||
|
|
||||||
type: function () {
|
|
||||||
return this.options.type || 'text'
|
|
||||||
},
|
|
||||||
|
|
||||||
value: function () {
|
|
||||||
return this.input.value || undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
onKeyDown: function (e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
L.DomEvent.stop(e)
|
|
||||||
this.finish()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.BlurInput = L.FormBuilder.Input.extend({
|
|
||||||
getSyncEvent: () => 'blur',
|
|
||||||
|
|
||||||
build: function () {
|
|
||||||
L.FormBuilder.Input.prototype.build.call(this)
|
|
||||||
L.DomEvent.on(this.input, 'focus', this.fetch, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
finish: function () {
|
|
||||||
this.sync()
|
|
||||||
L.FormBuilder.Input.prototype.finish.call(this)
|
|
||||||
},
|
|
||||||
|
|
||||||
sync: function () {
|
|
||||||
// Do not commit any change if user only clicked
|
|
||||||
// on the field than clicked outside
|
|
||||||
if (this.initial !== this.value()) {
|
|
||||||
L.FormBuilder.Input.prototype.sync.call(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.IntegerMixin = {
|
|
||||||
value: function () {
|
|
||||||
return !isNaN(this.input.value) && this.input.value !== ''
|
|
||||||
? parseInt(this.input.value, 10)
|
|
||||||
: undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
type: () => 'number',
|
|
||||||
}
|
|
||||||
|
|
||||||
L.FormBuilder.IntInput = L.FormBuilder.Input.extend({
|
|
||||||
includes: [L.FormBuilder.IntegerMixin],
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.BlurIntInput = L.FormBuilder.BlurInput.extend({
|
|
||||||
includes: [L.FormBuilder.IntegerMixin],
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.FloatMixin = {
|
|
||||||
value: function () {
|
|
||||||
return !isNaN(this.input.value) && this.input.value !== ''
|
|
||||||
? parseFloat(this.input.value)
|
|
||||||
: undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
type: () => 'number',
|
|
||||||
}
|
|
||||||
|
|
||||||
L.FormBuilder.FloatInput = L.FormBuilder.Input.extend({
|
|
||||||
options: {
|
|
||||||
step: 'any',
|
|
||||||
},
|
|
||||||
|
|
||||||
includes: [L.FormBuilder.FloatMixin],
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.BlurFloatInput = L.FormBuilder.BlurInput.extend({
|
|
||||||
options: {
|
|
||||||
step: 'any',
|
|
||||||
},
|
|
||||||
|
|
||||||
includes: [L.FormBuilder.FloatMixin],
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.CheckBox = L.FormBuilder.Element.extend({
|
|
||||||
build: function () {
|
|
||||||
const container = L.DomUtil.create('div', 'checkbox-wrapper', this.parentNode)
|
|
||||||
this.input = L.DomUtil.create('input', this.options.className || '', container)
|
|
||||||
this.input.type = 'checkbox'
|
|
||||||
this.input.name = this.name
|
|
||||||
this.input._helper = this
|
|
||||||
this.fetch()
|
|
||||||
L.DomEvent.on(this.input, 'change', this.sync, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: function () {
|
|
||||||
this.initial = this.toHTML()
|
|
||||||
this.input.checked = this.initial === true
|
|
||||||
},
|
|
||||||
|
|
||||||
value: function () {
|
|
||||||
return this.input.checked
|
|
||||||
},
|
|
||||||
|
|
||||||
toHTML: function () {
|
|
||||||
return [1, true].indexOf(this.get()) !== -1
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.Select = L.FormBuilder.Element.extend({
|
|
||||||
selectOptions: [['value', 'label']],
|
|
||||||
|
|
||||||
build: function () {
|
|
||||||
this.select = L.DomUtil.create('select', '', this.parentNode)
|
|
||||||
this.select.name = this.name
|
|
||||||
this.validValues = []
|
|
||||||
this.buildOptions()
|
|
||||||
L.DomEvent.on(this.select, 'change', this.sync, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
getOptions: function () {
|
|
||||||
return this.options.selectOptions || this.selectOptions
|
|
||||||
},
|
|
||||||
|
|
||||||
fetch: function () {
|
|
||||||
this.buildOptions()
|
|
||||||
},
|
|
||||||
|
|
||||||
buildOptions: function () {
|
|
||||||
this.select.innerHTML = ''
|
|
||||||
for (const option of this.getOptions()) {
|
|
||||||
if (typeof option === 'string') this.buildOption(option, option)
|
|
||||||
else this.buildOption(option[0], option[1])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
buildOption: function (value, label) {
|
|
||||||
this.validValues.push(value)
|
|
||||||
const option = L.DomUtil.create('option', '', this.select)
|
|
||||||
option.value = value
|
|
||||||
option.innerHTML = label
|
|
||||||
if (this.toHTML() === value) {
|
|
||||||
option.selected = 'selected'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
value: function () {
|
|
||||||
if (this.select[this.select.selectedIndex])
|
|
||||||
return this.select[this.select.selectedIndex].value
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefault: function () {
|
|
||||||
return this.getOptions()[0][0]
|
|
||||||
},
|
|
||||||
|
|
||||||
toJS: function () {
|
|
||||||
const value = this.value()
|
|
||||||
if (this.validValues.indexOf(value) !== -1) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return this.getDefault()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.IntSelect = L.FormBuilder.Select.extend({
|
|
||||||
value: function () {
|
|
||||||
return parseInt(L.FormBuilder.Select.prototype.value.apply(this), 10)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
L.FormBuilder.NullableBoolean = L.FormBuilder.Select.extend({
|
|
||||||
selectOptions: [
|
|
||||||
[undefined, 'inherit'],
|
|
||||||
[true, 'yes'],
|
|
||||||
[false, 'no'],
|
|
||||||
],
|
|
||||||
|
|
||||||
toJS: function () {
|
|
||||||
let value = this.value()
|
|
||||||
switch (value) {
|
|
||||||
case 'true':
|
|
||||||
case true:
|
|
||||||
value = true
|
|
||||||
break
|
|
||||||
case 'false':
|
|
||||||
case false:
|
|
||||||
value = false
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
value = undefined
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -30,8 +30,6 @@
|
||||||
<script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}"
|
<script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}"
|
||||||
defer></script>
|
defer></script>
|
||||||
<script src="{% static 'umap/vendors/toolbar/leaflet.toolbar.js' %}" defer></script>
|
<script src="{% static 'umap/vendors/toolbar/leaflet.toolbar.js' %}" defer></script>
|
||||||
<script src="{% static 'umap/vendors/formbuilder/Leaflet.FormBuilder.js' %}"
|
|
||||||
defer></script>
|
|
||||||
<script src="{% static 'umap/vendors/measurable/Leaflet.Measurable.js' %}"
|
<script src="{% static 'umap/vendors/measurable/Leaflet.Measurable.js' %}"
|
||||||
defer></script>
|
defer></script>
|
||||||
<script src="{% static 'umap/vendors/iconlayers/iconLayers.js' %}" defer></script>
|
<script src="{% static 'umap/vendors/iconlayers/iconLayers.js' %}" defer></script>
|
||||||
|
@ -40,7 +38,6 @@
|
||||||
<script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}"
|
<script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}"
|
||||||
defer></script>
|
defer></script>
|
||||||
<script src="{% static 'umap/js/umap.core.js' %}" defer></script>
|
<script src="{% static 'umap/js/umap.core.js' %}" defer></script>
|
||||||
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
|
|
||||||
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
||||||
<script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
<script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
|
|
|
@ -103,7 +103,7 @@ def test_can_change_icon_class(live_server, openmap, page):
|
||||||
expect(page.locator(".umap-circle-icon")).to_be_hidden()
|
expect(page.locator(".umap-circle-icon")).to_be_hidden()
|
||||||
page.locator(".panel.right").get_by_title("Edit", exact=True).click()
|
page.locator(".panel.right").get_by_title("Edit", exact=True).click()
|
||||||
page.get_by_text("Shape properties").click()
|
page.get_by_text("Shape properties").click()
|
||||||
page.locator(".umap-field-iconClass a.define").click()
|
page.locator(".umap-field-iconClass button.define").click()
|
||||||
page.get_by_text("Circle", exact=True).click()
|
page.get_by_text("Circle", exact=True).click()
|
||||||
expect(page.locator(".umap-circle-icon")).to_be_visible()
|
expect(page.locator(".umap-circle-icon")).to_be_visible()
|
||||||
expect(page.locator(".umap-div-icon")).to_be_hidden()
|
expect(page.locator(".umap-div-icon")).to_be_hidden()
|
||||||
|
|
|
@ -60,8 +60,8 @@ def test_zoomcontrol_impacts_ui(live_server, page, tilelayer):
|
||||||
# Hide them
|
# Hide them
|
||||||
page.get_by_text("User interface options").click()
|
page.get_by_text("User interface options").click()
|
||||||
hide_zoom_controls = (
|
hide_zoom_controls = (
|
||||||
page.locator("div")
|
page.locator(".panel")
|
||||||
.filter(has_text=re.compile(r"^Display the zoom control"))
|
.filter(has_text=re.compile("Display the zoom control"))
|
||||||
.locator("label")
|
.locator("label")
|
||||||
.nth(2)
|
.nth(2)
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,7 +101,7 @@ def test_can_remove_stroke(live_server, openmap, page, bootstrap):
|
||||||
page.get_by_role("link", name="Toggle edit mode").click()
|
page.get_by_role("link", name="Toggle edit mode").click()
|
||||||
page.get_by_text("Shape properties").click()
|
page.get_by_text("Shape properties").click()
|
||||||
page.locator(".umap-field-stroke .define").first.click()
|
page.locator(".umap-field-stroke .define").first.click()
|
||||||
page.locator(".umap-field-stroke label").first.click()
|
page.locator(".umap-field-stroke .show-on-defined label").first.click()
|
||||||
expect(page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']")).to_have_count(
|
expect(page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']")).to_have_count(
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ def test_can_change_picto_at_map_level(openmap, live_server, page, pictos):
|
||||||
define.click()
|
define.click()
|
||||||
# No picto defined yet, so recent should not be visible
|
# No picto defined yet, so recent should not be visible
|
||||||
expect(page.get_by_text("Recent")).to_be_hidden()
|
expect(page.get_by_text("Recent")).to_be_hidden()
|
||||||
symbols = page.locator(".umap-pictogram-choice")
|
symbols = page.locator(".umap-pictogram-body .umap-pictogram-choice")
|
||||||
expect(symbols).to_have_count(2)
|
expect(symbols).to_have_count(2)
|
||||||
search = page.locator(".umap-pictogram-body input")
|
search = page.locator(".umap-pictogram-body input")
|
||||||
search.type("star")
|
search.type("star")
|
||||||
|
@ -90,8 +90,8 @@ def test_can_change_picto_at_datalayer_level(openmap, live_server, page, pictos)
|
||||||
expect(define).to_be_visible()
|
expect(define).to_be_visible()
|
||||||
expect(undefine).to_be_hidden()
|
expect(undefine).to_be_hidden()
|
||||||
define.click()
|
define.click()
|
||||||
# Map has an icon defined, so it shold open on Recent tab
|
# Map has an icon defined, so it should open on Recent tab
|
||||||
symbols = page.locator(".umap-pictogram-choice")
|
symbols = page.locator(".umap-pictogram-body .umap-pictogram-choice")
|
||||||
expect(page.get_by_text("Recent")).to_be_visible()
|
expect(page.get_by_text("Recent")).to_be_visible()
|
||||||
expect(symbols).to_have_count(1)
|
expect(symbols).to_have_count(1)
|
||||||
symbol_tab = page.get_by_role("button", name="Symbol")
|
symbol_tab = page.get_by_role("button", name="Symbol")
|
||||||
|
@ -128,8 +128,8 @@ def test_can_change_picto_at_marker_level(openmap, live_server, page, pictos):
|
||||||
expect(define).to_be_visible()
|
expect(define).to_be_visible()
|
||||||
expect(undefine).to_be_hidden()
|
expect(undefine).to_be_hidden()
|
||||||
define.click()
|
define.click()
|
||||||
# Map has an icon defined, so it shold open on Recent tab
|
# Map has an icon defined, so it shuold open on Recent tab
|
||||||
symbols = page.locator(".umap-pictogram-choice")
|
symbols = page.locator(".umap-pictogram-body .umap-pictogram-choice")
|
||||||
expect(page.get_by_text("Recent")).to_be_visible()
|
expect(page.get_by_text("Recent")).to_be_visible()
|
||||||
expect(symbols).to_have_count(1)
|
expect(symbols).to_have_count(1)
|
||||||
symbol_tab = page.get_by_role("button", name="Symbol")
|
symbol_tab = page.get_by_role("button", name="Symbol")
|
||||||
|
@ -180,7 +180,7 @@ def test_can_use_remote_url_as_picto(openmap, live_server, page, pictos):
|
||||||
expect(modify).to_be_visible()
|
expect(modify).to_be_visible()
|
||||||
modify.click()
|
modify.click()
|
||||||
# Should be on Recent tab
|
# Should be on Recent tab
|
||||||
symbols = page.locator(".umap-pictogram-choice")
|
symbols = page.locator(".umap-pictogram-body .umap-pictogram-choice")
|
||||||
expect(page.get_by_text("Recent")).to_be_visible()
|
expect(page.get_by_text("Recent")).to_be_visible()
|
||||||
expect(symbols).to_have_count(1)
|
expect(symbols).to_have_count(1)
|
||||||
|
|
||||||
|
@ -215,10 +215,10 @@ def test_can_use_char_as_picto(openmap, live_server, page, pictos):
|
||||||
close.click()
|
close.click()
|
||||||
edit_settings.click()
|
edit_settings.click()
|
||||||
shape_settings.click()
|
shape_settings.click()
|
||||||
preview = page.locator(".umap-pictogram-choice")
|
preview = page.locator(".header .umap-pictogram-choice")
|
||||||
expect(preview).to_be_visible()
|
expect(preview).to_be_visible()
|
||||||
preview.click()
|
preview.click()
|
||||||
# Should be on URL tab
|
# Should be on URL tab
|
||||||
symbols = page.locator(".umap-pictogram-choice")
|
symbols = page.locator(".umap-pictogram-body .umap-pictogram-choice")
|
||||||
expect(page.get_by_text("Recent")).to_be_visible()
|
expect(page.get_by_text("Recent")).to_be_visible()
|
||||||
expect(symbols).to_have_count(1)
|
expect(symbols).to_have_count(1)
|
||||||
|
|
|
@ -187,9 +187,11 @@ def test_websocket_connection_can_sync_map_properties(
|
||||||
# Zoom control is synced
|
# Zoom control is synced
|
||||||
peerB.get_by_role("link", name="Map advanced properties").click()
|
peerB.get_by_role("link", name="Map advanced properties").click()
|
||||||
peerB.locator("summary").filter(has_text="User interface options").click()
|
peerB.locator("summary").filter(has_text="User interface options").click()
|
||||||
peerB.locator("div").filter(
|
switch = peerB.locator("div.formbox").filter(
|
||||||
has_text=re.compile(r"^Display the zoom control")
|
has_text=re.compile("Display the zoom control")
|
||||||
).locator("label").nth(2).click()
|
)
|
||||||
|
expect(switch).to_be_visible()
|
||||||
|
switch.get_by_text("Never").click()
|
||||||
|
|
||||||
expect(peerA.locator(".leaflet-control-zoom")).to_be_hidden()
|
expect(peerA.locator(".leaflet-control-zoom")).to_be_hidden()
|
||||||
|
|
||||||
|
@ -278,7 +280,7 @@ def test_websocket_connection_can_sync_cloned_polygons(
|
||||||
peerB.locator("path").nth(1).drag_to(b_map_el, target_position={"x": 400, "y": 400})
|
peerB.locator("path").nth(1).drag_to(b_map_el, target_position={"x": 400, "y": 400})
|
||||||
peerB.locator("path").nth(1).click()
|
peerB.locator("path").nth(1).click()
|
||||||
peerB.locator("summary").filter(has_text="Shape properties").click()
|
peerB.locator("summary").filter(has_text="Shape properties").click()
|
||||||
peerB.locator(".header > a:nth-child(2)").first.click()
|
peerB.locator(".umap-field-color button.define").first.click()
|
||||||
peerB.get_by_title("Orchid", exact=True).first.click()
|
peerB.get_by_title("Orchid", exact=True).first.click()
|
||||||
peerB.locator("#map").press("Escape")
|
peerB.locator("#map").press("Escape")
|
||||||
peerB.get_by_role("button", name="Save").click()
|
peerB.get_by_role("button", name="Save").click()
|
||||||
|
|
Loading…
Reference in a new issue