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
|
@ -1,6 +1,7 @@
|
||||||
import { Translation, TypedTranslation } from "../UI/i18n/Translation"
|
import { Translation, TypedTranslation } from "../UI/i18n/Translation"
|
||||||
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
||||||
import Translations from "../UI/i18n/Translations"
|
import Translations from "../UI/i18n/Translations"
|
||||||
|
import { Validator } from "../UI/InputElement/Validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A 'denomination' is one way to write a certain quantity.
|
* A 'denomination' is one way to write a certain quantity.
|
||||||
|
@ -15,6 +16,7 @@ export class Denomination {
|
||||||
public readonly alternativeDenominations: string[]
|
public readonly alternativeDenominations: string[]
|
||||||
public readonly human: TypedTranslation<{ quantity: string }>
|
public readonly human: TypedTranslation<{ quantity: string }>
|
||||||
public readonly humanSingular?: Translation
|
public readonly humanSingular?: Translation
|
||||||
|
private readonly _validator: Validator
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
canonical: string,
|
canonical: string,
|
||||||
|
@ -24,7 +26,8 @@ export class Denomination {
|
||||||
addSpace: boolean,
|
addSpace: boolean,
|
||||||
alternativeDenominations: string[],
|
alternativeDenominations: string[],
|
||||||
_human: TypedTranslation<{ quantity: string }>,
|
_human: TypedTranslation<{ quantity: string }>,
|
||||||
_humanSingular?: Translation
|
_humanSingular: Translation,
|
||||||
|
validator: Validator
|
||||||
) {
|
) {
|
||||||
this.canonical = canonical
|
this.canonical = canonical
|
||||||
this._canonicalSingular = _canonicalSingular
|
this._canonicalSingular = _canonicalSingular
|
||||||
|
@ -34,9 +37,10 @@ export class Denomination {
|
||||||
this.alternativeDenominations = alternativeDenominations
|
this.alternativeDenominations = alternativeDenominations
|
||||||
this.human = _human
|
this.human = _human
|
||||||
this.humanSingular = _humanSingular
|
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})`
|
context = `${context}.unit(${json.canonicalDenomination})`
|
||||||
const canonical = json.canonicalDenomination.trim()
|
const canonical = json.canonicalDenomination.trim()
|
||||||
if (canonical === undefined) {
|
if (canonical === undefined) {
|
||||||
|
@ -68,7 +72,8 @@ export class Denomination {
|
||||||
json.addSpace ?? false,
|
json.addSpace ?? false,
|
||||||
json.alternativeDenomination?.map((v) => v.trim()) ?? [],
|
json.alternativeDenomination?.map((v) => v.trim()) ?? [],
|
||||||
humanTexts,
|
humanTexts,
|
||||||
Translations.T(json.humanSingular, context + "humanSingular")
|
Translations.T(json.humanSingular, context + "humanSingular"),
|
||||||
|
validator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +86,8 @@ export class Denomination {
|
||||||
this.addSpace,
|
this.addSpace,
|
||||||
this.alternativeDenominations,
|
this.alternativeDenominations,
|
||||||
this.human,
|
this.human,
|
||||||
this.humanSingular
|
this.humanSingular,
|
||||||
|
this._validator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +100,8 @@ export class Denomination {
|
||||||
this.addSpace,
|
this.addSpace,
|
||||||
[this.canonical, ...this.alternativeDenominations],
|
[this.canonical, ...this.alternativeDenominations],
|
||||||
this.human,
|
this.human,
|
||||||
this.humanSingular
|
this.humanSingular,
|
||||||
|
this._validator
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,11 +206,13 @@ export class Denomination {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = Number(value.trim())
|
if(!this._validator.isValid(value.trim())){
|
||||||
if (!isNaN(parsed)) {
|
return null
|
||||||
return value.trim()
|
|
||||||
}
|
}
|
||||||
|
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.allowSplit = json.allowSplit ?? false
|
||||||
this.name = Translations.T(json.name, translationContext + ".name")
|
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 (json.description !== undefined) {
|
||||||
if (Object.keys(json.description).length === 0) {
|
if (Object.keys(json.description).length === 0) {
|
||||||
|
@ -280,6 +268,18 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
this.id + ".tagRenderings[" + i + "]"
|
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 (
|
if (
|
||||||
json.filter !== undefined &&
|
json.filter !== undefined &&
|
||||||
|
|
|
@ -2,6 +2,10 @@ import BaseUIElement from "../UI/BaseUIElement"
|
||||||
import { Denomination } from "./Denomination"
|
import { Denomination } from "./Denomination"
|
||||||
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
|
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
|
||||||
import unit from "../../assets/layers/unit/unit.json"
|
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 {
|
export class Unit {
|
||||||
private static allUnits = this.initUnits()
|
private static allUnits = this.initUnits()
|
||||||
|
@ -10,14 +14,17 @@ export class Unit {
|
||||||
public readonly denominationsSorted: Denomination[]
|
public readonly denominationsSorted: Denomination[]
|
||||||
public readonly eraseInvalid: boolean
|
public readonly eraseInvalid: boolean
|
||||||
public readonly quantity: string
|
public readonly quantity: string
|
||||||
|
private readonly _validator: Validator
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
quantity: string,
|
quantity: string,
|
||||||
appliesToKeys: string[],
|
appliesToKeys: string[],
|
||||||
applicableDenominations: Denomination[],
|
applicableDenominations: Denomination[],
|
||||||
eraseInvalid: boolean
|
eraseInvalid: boolean,
|
||||||
|
validator: Validator
|
||||||
) {
|
) {
|
||||||
this.quantity = quantity
|
this.quantity = quantity
|
||||||
|
this._validator = validator
|
||||||
this.appliesToKeys = new Set(appliesToKeys)
|
this.appliesToKeys = new Set(appliesToKeys)
|
||||||
this.denominations = applicableDenominations
|
this.denominations = applicableDenominations
|
||||||
this.eraseInvalid = eraseInvalid
|
this.eraseInvalid = eraseInvalid
|
||||||
|
@ -67,12 +74,46 @@ export class Unit {
|
||||||
json:
|
json:
|
||||||
| UnitConfigJson
|
| UnitConfigJson
|
||||||
| Record<string, string | { quantity: string; denominations: string[] }>,
|
| Record<string, string | { quantity: string; denominations: string[] }>,
|
||||||
|
tagRenderings: TagRenderingConfig[],
|
||||||
ctx: string
|
ctx: string
|
||||||
): Unit[] {
|
): 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")
|
* }, "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
|
const appliesTo = json.appliesToKey
|
||||||
for (let i = 0; i < (appliesTo ?? []).length; i++) {
|
for (let i = 0; i < (appliesTo ?? []).length; i++) {
|
||||||
let key = appliesTo[i]
|
let key = appliesTo[i]
|
||||||
|
@ -127,32 +168,22 @@ export class Unit {
|
||||||
}
|
}
|
||||||
// Some keys do have unit handling
|
// Some keys do have unit handling
|
||||||
|
|
||||||
const applicable = json.applicableUnits.map((u, i) =>
|
|
||||||
Denomination.fromJson(u, `${ctx}.units[${i}]`)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
const units: Unit[] = []
|
||||||
json.defaultInput &&
|
if (appliesTo === undefined) {
|
||||||
!applicable.some((denom) => denom.canonical.trim() === json.defaultInput)
|
units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx))
|
||||||
) {
|
|
||||||
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(
|
for (const key of appliesTo ?? []) {
|
||||||
json.quantity ?? "",
|
const validator = Validators.get(types[key] ?? "float")
|
||||||
appliesTo,
|
units.push(this.parseDenomination(json, validator, undefined, ctx))
|
||||||
applicable,
|
}
|
||||||
json.eraseInvalidValues ?? false
|
return units
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static initUnits(): Map<string, Unit> {
|
private static initUnits(): Map<string, Unit> {
|
||||||
const m = new Map<string, Unit>()
|
const m = new Map<string, Unit>()
|
||||||
const units = (<UnitConfigJson[]>unit.units).map((json, i) =>
|
const units = (<UnitConfigJson[]>unit.units).flatMap((json, i) =>
|
||||||
this.parse(json, "unit.json.units." + i)
|
this.parse(json, {}, "unit.json.units." + i)
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const unit of units) {
|
for (const unit of units) {
|
||||||
|
@ -181,15 +212,17 @@ export class Unit {
|
||||||
string,
|
string,
|
||||||
string | { quantity: string; denominations: string[]; canonical?: string }
|
string | { quantity: string; denominations: string[]; canonical?: string }
|
||||||
>,
|
>,
|
||||||
|
types: Record<string, ValidatorType>,
|
||||||
ctx: string
|
ctx: string
|
||||||
): Unit[] {
|
): Unit[] {
|
||||||
const units: Unit[] = []
|
const units: Unit[] = []
|
||||||
for (const key in spec) {
|
for (const key in spec) {
|
||||||
const toLoad = spec[key]
|
const toLoad = spec[key]
|
||||||
|
const validator = Validators.get(types[key] ?? "float")
|
||||||
if (typeof toLoad === "string") {
|
if (typeof toLoad === "string") {
|
||||||
const loaded = this.getFromLibrary(toLoad, ctx)
|
const loaded = this.getFromLibrary(toLoad, ctx)
|
||||||
units.push(
|
units.push(
|
||||||
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid)
|
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid, validator)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -213,12 +246,13 @@ export class Unit {
|
||||||
const denoms = toLoad.denominations
|
const denoms = toLoad.denominations
|
||||||
.map((d) => d.toLowerCase())
|
.map((d) => d.toLowerCase())
|
||||||
.map((d) => fetchDenom(d))
|
.map((d) => fetchDenom(d))
|
||||||
|
.map(d => d.withValidator(validator))
|
||||||
|
|
||||||
if (toLoad.canonical) {
|
if (toLoad.canonical) {
|
||||||
const canonical = fetchDenom(toLoad.canonical)
|
const canonical = fetchDenom(toLoad.canonical).withValidator(validator)
|
||||||
denoms.unshift(canonical.withBlankCanonical())
|
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
|
return units
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,11 +91,6 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit !== undefined && isNaN(Number(v))) {
|
|
||||||
value.setData(undefined)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
feedback?.setData(undefined)
|
feedback?.setData(undefined)
|
||||||
if (selectedUnit.data) {
|
if (selectedUnit.data) {
|
||||||
value.setData(unit.toOsm(v, selectedUnit.data))
|
value.setData(unit.toOsm(v, selectedUnit.data))
|
||||||
|
|
|
@ -178,6 +178,7 @@
|
||||||
checkedMappings,
|
checkedMappings,
|
||||||
tags.data
|
tags.data
|
||||||
)
|
)
|
||||||
|
console.log("Constructing change spec from", {freeform: $freeformInput, selectedMapping, checkedMappings, currentTags: tags.data}, " --> ", selectedTags)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not calculate changeSpecification:", e)
|
console.error("Could not calculate changeSpecification:", e)
|
||||||
selectedTags = undefined
|
selectedTags = undefined
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue