feat: very minimal experimental conditional style rules

This commit is contained in:
Yohan Boniface 2024-04-25 16:30:46 +02:00
parent b94995120a
commit 05eab25da4
5 changed files with 194 additions and 2 deletions

View file

@ -5,6 +5,7 @@ import Caption from './caption.js'
import { Panel, EditPanel, FullPanel } from './ui/panel.js' import { Panel, EditPanel, FullPanel } from './ui/panel.js'
import Dialog from './ui/dialog.js' import Dialog from './ui/dialog.js'
import Tooltip from './ui/tooltip.js' import Tooltip from './ui/tooltip.js'
import Rules from './rules.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
import { SCHEMA } from './schema.js' import { SCHEMA } from './schema.js'
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js' import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'
@ -43,6 +44,7 @@ window.U = {
Panel, Panel,
Request, Request,
RequestError, RequestError,
Rules,
SCHEMA, SCHEMA,
ServerRequest, ServerRequest,
SyncEngine, SyncEngine,

View file

@ -0,0 +1,175 @@
import { DomUtil, DomEvent, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
import * as Utils from './utils.js'
import { translate } from './i18n.js'
class Rule {
constructor(map, condition = '', options = {}) {
this.map = map
this.condition = condition
this.parse()
this.options = options
let isDirty = false
Object.defineProperty(this, 'isDirty', {
get: () => {
return isDirty
},
set: (status) => {
isDirty = status
if (status) {
this.map.isDirty = status
}
},
})
}
render(fields) {
this.map.render(fields)
}
parse() {
let vars = []
if (this.condition.includes('!=')) {
this.operator = (our, other) => our != other
vars = this.condition.split('!=')
} else if (this.condition.includes('=')) {
this.operator = (our, other) => our === other
vars = this.condition.split('=')
}
if (vars.length != 2) this.operator = undefined
this.key = vars[0]
this.expected = vars[1]
}
match(props) {
if (!this.operator) return false
return this.operator(this.expected, props[this.key])
}
getMap() {
return this.map
}
getOption(option) {
return this.options[option]
}
edit() {
const options = [
[
'condition',
{
handler: 'BlurInput',
label: L._('Condition'),
placeholder: translate('key=value or key!=value'),
},
],
'options.color',
'options.iconClass',
'options.iconUrl',
'options.iconOpacity',
'options.opacity',
'options.weight',
'options.fill',
'options.fillColor',
'options.fillOpacity',
'options.smoothFactor',
'options.dashArray',
]
const container = DomUtil.create('div')
const builder = new U.FormBuilder(this, options)
const defaultShapeProperties = L.DomUtil.add('div', '', container)
defaultShapeProperties.appendChild(builder.build())
this.map.editPanel.open({ content: container })
}
renderToolbox(container) {
const toggle = L.DomUtil.createButtonIcon(
container,
'icon-eye',
L._('Show/hide layer')
)
const edit = L.DomUtil.createButtonIcon(
container,
'icon-edit show-on-edit',
L._('Edit')
)
const remove = L.DomUtil.createButtonIcon(
container,
'icon-delete show-on-edit',
L._('Delete layer')
)
L.DomEvent.on(edit, 'click', this.edit, this)
L.DomEvent.on(
remove,
'click',
function () {
if (!confirm(L._('Are you sure you want to delete this rule?'))) return
this._delete()
this.map.editPanel.close()
},
this
)
DomUtil.add('span', '', container, this.condition || translate('empty rule'))
//L.DomEvent.on(toggle, 'click', this.toggle, this)
}
_delete() {
this.map.rules.rules = this.map.rules.rules.filter((rule) => rule != this)
}
}
export default class Rules {
constructor(map) {
this.map = map
this.rules = []
this.loadRules()
}
loadRules() {
if (!this.map.options.rules?.length) return
for (const { condition, options } of this.map.options.rules) {
if (!condition) continue
this.rules.push(new Rule(this.map, condition, options))
}
}
edit(container) {
const body = L.DomUtil.createFieldset(
container,
translate('Conditional style rules')
)
if (this.rules.length) {
const list = DomUtil.create('ul', '', body)
for (const rule of this.rules) {
rule.renderToolbox(DomUtil.create('li', '', list))
}
}
L.DomUtil.createButton('umap-add', body, translate('Add rule'), this.addRule, this)
}
addRule() {
const rule = new Rule(this.map)
rule.isDirty = true
this.rules.push(rule)
rule.edit(map)
}
commit() {
this.map.options.rules = this.rules.map((rule) => {
return {
condition: rule.condition,
options: rule.options,
}
})
}
getOption(option, feature) {
for (const rule of this.rules) {
if (rule.match(feature.properties)) {
if (Utils.usableOption(rule.options, option)) return rule.options[option]
break
}
}
}
}

View file

@ -386,6 +386,10 @@ export const SCHEMA = {
type: Object, type: Object,
impacts: ['remote-data'], impacts: ['remote-data'],
}, },
rules: {
type: Object,
impacts: ['data'],
},
scaleControl: { scaleControl: {
type: Boolean, type: Boolean,
impacts: ['ui'], impacts: ['ui'],

View file

@ -419,6 +419,7 @@ U.Map = L.Map.extend({
this.importer = new U.Importer(this) this.importer = new U.Importer(this)
this.drop = new U.DropControl(this) this.drop = new U.DropControl(this)
this.share = new U.Share(this) this.share = new U.Share(this)
this.rules = new U.Rules(this)
this._controls.tilelayers = new U.TileLayerControl(this) this._controls.tilelayers = new U.TileLayerControl(this)
}, },
@ -823,7 +824,15 @@ U.Map = L.Map.extend({
return U.SCHEMA[option] && U.SCHEMA[option].default return U.SCHEMA[option] && U.SCHEMA[option].default
}, },
getOption: function (option) { getRuleOption: function (option, feature) {
return this.rules.getOption(option, feature)
},
getOption: function (option, feature) {
if (feature) {
const value = this.getRuleOption(option, feature)
if (value !== undefined) return value
}
if (U.Utils.usableOption(this.options, option)) return this.options[option] if (U.Utils.usableOption(this.options, option)) return this.options[option]
return this.getDefaultOption(option) return this.getDefaultOption(option)
}, },
@ -1031,6 +1040,7 @@ U.Map = L.Map.extend({
}, },
saveSelf: async function () { saveSelf: async function () {
this.rules.commit()
const geojson = { const geojson = {
type: 'Feature', type: 'Feature',
geometry: this.geometry(), geometry: this.geometry(),
@ -1561,6 +1571,7 @@ U.Map = L.Map.extend({
this._editShapeProperties(container) this._editShapeProperties(container)
this._editDefaultProperties(container) this._editDefaultProperties(container)
this._editInteractionsProperties(container) this._editInteractionsProperties(container)
this.rules.edit(container)
this._editTilelayer(container) this._editTilelayer(container)
this._editOverlay(container) this._editOverlay(container)
this._editBounds(container) this._editBounds(container)

View file

@ -1496,7 +1496,7 @@ U.DataLayer = L.Evented.extend({
} else if (this.layer && this.layer.defaults && this.layer.defaults[option]) { } else if (this.layer && this.layer.defaults && this.layer.defaults[option]) {
return this.layer.defaults[option] return this.layer.defaults[option]
} else { } else {
return this.map.getOption(option) return this.map.getOption(option, feature)
} }
}, },