Themes: allow to have a non-number type together with a unit

This commit is contained in:
Pieter Vander Vennet 2024-04-28 22:13:25 +02:00
parent b4163897e8
commit 4ccfe3efe4
5 changed files with 93 additions and 54 deletions

View file

@ -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)
}
}

View file

@ -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 &&

View file

@ -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
}

View file

@ -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))

View file

@ -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