forked from MapComplete/MapComplete
Themes: allow to have a non-number type together with a unit
This commit is contained in:
parent
b4163897e8
commit
4ccfe3efe4
5 changed files with 93 additions and 54 deletions
src
Models
UI
|
@ -1,6 +1,7 @@
|
|||
import { Translation, TypedTranslation } from "../UI/i18n/Translation"
|
||||
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
||||
import Translations from "../UI/i18n/Translations"
|
||||
import { Validator } from "../UI/InputElement/Validator"
|
||||
|
||||
/**
|
||||
* A 'denomination' is one way to write a certain quantity.
|
||||
|
@ -15,6 +16,7 @@ export class Denomination {
|
|||
public readonly alternativeDenominations: string[]
|
||||
public readonly human: TypedTranslation<{ quantity: string }>
|
||||
public readonly humanSingular?: Translation
|
||||
private readonly _validator: Validator
|
||||
|
||||
private constructor(
|
||||
canonical: string,
|
||||
|
@ -24,7 +26,8 @@ export class Denomination {
|
|||
addSpace: boolean,
|
||||
alternativeDenominations: string[],
|
||||
_human: TypedTranslation<{ quantity: string }>,
|
||||
_humanSingular?: Translation
|
||||
_humanSingular: Translation,
|
||||
validator: Validator
|
||||
) {
|
||||
this.canonical = canonical
|
||||
this._canonicalSingular = _canonicalSingular
|
||||
|
@ -34,9 +37,10 @@ export class Denomination {
|
|||
this.alternativeDenominations = alternativeDenominations
|
||||
this.human = _human
|
||||
this.humanSingular = _humanSingular
|
||||
this._validator = validator
|
||||
}
|
||||
|
||||
public static fromJson(json: DenominationConfigJson, context: string) {
|
||||
public static fromJson(json: DenominationConfigJson, validator: Validator, context: string) {
|
||||
context = `${context}.unit(${json.canonicalDenomination})`
|
||||
const canonical = json.canonicalDenomination.trim()
|
||||
if (canonical === undefined) {
|
||||
|
@ -68,7 +72,8 @@ export class Denomination {
|
|||
json.addSpace ?? false,
|
||||
json.alternativeDenomination?.map((v) => v.trim()) ?? [],
|
||||
humanTexts,
|
||||
Translations.T(json.humanSingular, context + "humanSingular")
|
||||
Translations.T(json.humanSingular, context + "humanSingular"),
|
||||
validator
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -81,7 +86,8 @@ export class Denomination {
|
|||
this.addSpace,
|
||||
this.alternativeDenominations,
|
||||
this.human,
|
||||
this.humanSingular
|
||||
this.humanSingular,
|
||||
this._validator
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -94,7 +100,8 @@ export class Denomination {
|
|||
this.addSpace,
|
||||
[this.canonical, ...this.alternativeDenominations],
|
||||
this.human,
|
||||
this.humanSingular
|
||||
this.humanSingular,
|
||||
this._validator
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -199,11 +206,13 @@ export class Denomination {
|
|||
return null
|
||||
}
|
||||
|
||||
const parsed = Number(value.trim())
|
||||
if (!isNaN(parsed)) {
|
||||
return value.trim()
|
||||
if(!this._validator.isValid(value.trim())){
|
||||
return null
|
||||
}
|
||||
return this._validator.reformat(value.trim())
|
||||
}
|
||||
|
||||
return null
|
||||
withValidator(validator: Validator) {
|
||||
return new Denomination(this.canonical, this._canonicalSingular, this.useIfNoUnitGiven, this.prefix, this.addSpace, this.alternativeDenominations, this.human, this.humanSingular, validator)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,18 +97,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
|
||||
this.allowSplit = json.allowSplit ?? false
|
||||
this.name = Translations.T(json.name, translationContext + ".name")
|
||||
if (json.units !== undefined && !Array.isArray(json.units)) {
|
||||
throw (
|
||||
"At " +
|
||||
context +
|
||||
".units: the 'units'-section should be a list; you probably have an object there"
|
||||
)
|
||||
}
|
||||
this.units = [].concat(
|
||||
...(json.units ?? []).map((unitJson, i) =>
|
||||
Unit.fromJson(unitJson, `${context}.unit[${i}]`)
|
||||
)
|
||||
)
|
||||
|
||||
if (json.description !== undefined) {
|
||||
if (Object.keys(json.description).length === 0) {
|
||||
|
@ -280,6 +268,18 @@ export default class LayerConfig extends WithContextLoader {
|
|||
this.id + ".tagRenderings[" + i + "]"
|
||||
)
|
||||
)
|
||||
if (json.units !== undefined && !Array.isArray(json.units)) {
|
||||
throw (
|
||||
"At " +
|
||||
context +
|
||||
".units: the 'units'-section should be a list; you probably have an object there"
|
||||
)
|
||||
}
|
||||
this.units = [].concat(
|
||||
...(json.units ?? []).map((unitJson, i) =>
|
||||
Unit.fromJson(unitJson, this.tagRenderings,`${context}.unit[${i}]`)
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
json.filter !== undefined &&
|
||||
|
|
|
@ -2,6 +2,10 @@ import BaseUIElement from "../UI/BaseUIElement"
|
|||
import { Denomination } from "./Denomination"
|
||||
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
|
||||
import unit from "../../assets/layers/unit/unit.json"
|
||||
import { QuestionableTagRenderingConfigJson } from "./ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig"
|
||||
import Validators, { ValidatorType } from "../UI/InputElement/Validators"
|
||||
import { Validator } from "../UI/InputElement/Validator"
|
||||
|
||||
export class Unit {
|
||||
private static allUnits = this.initUnits()
|
||||
|
@ -10,14 +14,17 @@ export class Unit {
|
|||
public readonly denominationsSorted: Denomination[]
|
||||
public readonly eraseInvalid: boolean
|
||||
public readonly quantity: string
|
||||
private readonly _validator: Validator
|
||||
|
||||
constructor(
|
||||
quantity: string,
|
||||
appliesToKeys: string[],
|
||||
applicableDenominations: Denomination[],
|
||||
eraseInvalid: boolean
|
||||
eraseInvalid: boolean,
|
||||
validator: Validator
|
||||
) {
|
||||
this.quantity = quantity
|
||||
this._validator = validator
|
||||
this.appliesToKeys = new Set(appliesToKeys)
|
||||
this.denominations = applicableDenominations
|
||||
this.eraseInvalid = eraseInvalid
|
||||
|
@ -67,12 +74,46 @@ export class Unit {
|
|||
json:
|
||||
| UnitConfigJson
|
||||
| Record<string, string | { quantity: string; denominations: string[] }>,
|
||||
tagRenderings: TagRenderingConfig[],
|
||||
ctx: string
|
||||
): Unit[] {
|
||||
if (!json.appliesToKey && !json.quantity) {
|
||||
return this.loadFromLibrary(<any>json, ctx)
|
||||
|
||||
let types: Record<string, ValidatorType> = {}
|
||||
for (const tagRendering of tagRenderings) {
|
||||
if (tagRendering.freeform?.type) {
|
||||
types[tagRendering.freeform.key] = tagRendering.freeform.type
|
||||
}
|
||||
}
|
||||
return [this.parse(<UnitConfigJson>json, ctx)]
|
||||
|
||||
if (!json.appliesToKey && !json.quantity) {
|
||||
return this.loadFromLibrary(<any>json, types, ctx)
|
||||
}
|
||||
return this.parse(<UnitConfigJson>json, types, ctx)
|
||||
}
|
||||
|
||||
private static parseDenomination(json: UnitConfigJson, validator: Validator, appliesToKey: string, ctx: string): Unit {
|
||||
const applicable = json.applicableUnits.map((u, i) =>
|
||||
Denomination.fromJson(u, validator, `${ctx}.units[${i}]`)
|
||||
)
|
||||
|
||||
if (
|
||||
json.defaultInput &&
|
||||
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
|
||||
) {
|
||||
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${
|
||||
json.defaultInput
|
||||
}', but the available denominations are ${applicable
|
||||
.map((denom) => denom.canonical)
|
||||
.join(", ")}`
|
||||
}
|
||||
|
||||
return new Unit(
|
||||
json.quantity ?? "",
|
||||
appliesToKey === undefined ? undefined : [appliesToKey],
|
||||
applicable,
|
||||
json.eraseInvalidValues ?? false,
|
||||
validator
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,7 +154,7 @@ export class Unit {
|
|||
* ]
|
||||
* }, "test")
|
||||
*/
|
||||
private static parse(json: UnitConfigJson, ctx: string): Unit {
|
||||
private static parse(json: UnitConfigJson, types: Record<string, ValidatorType>, ctx: string): Unit[] {
|
||||
const appliesTo = json.appliesToKey
|
||||
for (let i = 0; i < (appliesTo ?? []).length; i++) {
|
||||
let key = appliesTo[i]
|
||||
|
@ -127,32 +168,22 @@ export class Unit {
|
|||
}
|
||||
// Some keys do have unit handling
|
||||
|
||||
const applicable = json.applicableUnits.map((u, i) =>
|
||||
Denomination.fromJson(u, `${ctx}.units[${i}]`)
|
||||
)
|
||||
|
||||
if (
|
||||
json.defaultInput &&
|
||||
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
|
||||
) {
|
||||
throw `${ctx}: no denomination has the specified default denomination. The default denomination is '${
|
||||
json.defaultInput
|
||||
}', but the available denominations are ${applicable
|
||||
.map((denom) => denom.canonical)
|
||||
.join(", ")}`
|
||||
const units: Unit[] = []
|
||||
if (appliesTo === undefined) {
|
||||
units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx))
|
||||
}
|
||||
return new Unit(
|
||||
json.quantity ?? "",
|
||||
appliesTo,
|
||||
applicable,
|
||||
json.eraseInvalidValues ?? false
|
||||
)
|
||||
for (const key of appliesTo ?? []) {
|
||||
const validator = Validators.get(types[key] ?? "float")
|
||||
units.push(this.parseDenomination(json, validator, undefined, ctx))
|
||||
}
|
||||
return units
|
||||
}
|
||||
|
||||
private static initUnits(): Map<string, Unit> {
|
||||
const m = new Map<string, Unit>()
|
||||
const units = (<UnitConfigJson[]>unit.units).map((json, i) =>
|
||||
this.parse(json, "unit.json.units." + i)
|
||||
const units = (<UnitConfigJson[]>unit.units).flatMap((json, i) =>
|
||||
this.parse(json, {}, "unit.json.units." + i)
|
||||
)
|
||||
|
||||
for (const unit of units) {
|
||||
|
@ -181,15 +212,17 @@ export class Unit {
|
|||
string,
|
||||
string | { quantity: string; denominations: string[]; canonical?: string }
|
||||
>,
|
||||
types: Record<string, ValidatorType>,
|
||||
ctx: string
|
||||
): Unit[] {
|
||||
const units: Unit[] = []
|
||||
for (const key in spec) {
|
||||
const toLoad = spec[key]
|
||||
const validator = Validators.get(types[key] ?? "float")
|
||||
if (typeof toLoad === "string") {
|
||||
const loaded = this.getFromLibrary(toLoad, ctx)
|
||||
units.push(
|
||||
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid)
|
||||
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid, validator)
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
@ -213,12 +246,13 @@ export class Unit {
|
|||
const denoms = toLoad.denominations
|
||||
.map((d) => d.toLowerCase())
|
||||
.map((d) => fetchDenom(d))
|
||||
.map(d => d.withValidator(validator))
|
||||
|
||||
if (toLoad.canonical) {
|
||||
const canonical = fetchDenom(toLoad.canonical)
|
||||
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
|
||||
denoms.unshift(canonical.withBlankCanonical())
|
||||
}
|
||||
units.push(new Unit(loaded.quantity, [key], denoms, loaded.eraseInvalid))
|
||||
units.push(new Unit(loaded.quantity, [key], denoms, loaded.eraseInvalid, validator))
|
||||
}
|
||||
return units
|
||||
}
|
||||
|
|
|
@ -91,11 +91,6 @@
|
|||
return
|
||||
}
|
||||
|
||||
if (unit !== undefined && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
if (selectedUnit.data) {
|
||||
value.setData(unit.toOsm(v, selectedUnit.data))
|
||||
|
|
|
@ -178,6 +178,7 @@
|
|||
checkedMappings,
|
||||
tags.data
|
||||
)
|
||||
console.log("Constructing change spec from", {freeform: $freeformInput, selectedMapping, checkedMappings, currentTags: tags.data}, " --> ", selectedTags)
|
||||
} catch (e) {
|
||||
console.error("Could not calculate changeSpecification:", e)
|
||||
selectedTags = undefined
|
||||
|
|
Loading…
Add table
Reference in a new issue