From 4ccfe3efe446b407be1ca53d4ad2c3ee6fdde0b4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 28 Apr 2024 22:13:25 +0200 Subject: [PATCH] Themes: allow to have a non-number type together with a unit --- src/Models/Denomination.ts | 27 ++++-- src/Models/ThemeConfig/LayerConfig.ts | 24 ++--- src/Models/Unit.ts | 90 +++++++++++++------ src/UI/InputElement/ValidatedInput.svelte | 5 -- .../TagRendering/TagRenderingQuestion.svelte | 1 + 5 files changed, 93 insertions(+), 54 deletions(-) diff --git a/src/Models/Denomination.ts b/src/Models/Denomination.ts index 4ec3ffb91b..c7d8981912 100644 --- a/src/Models/Denomination.ts +++ b/src/Models/Denomination.ts @@ -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) } } diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index 49cea46b70..5d0a15fc04 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -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 && diff --git a/src/Models/Unit.ts b/src/Models/Unit.ts index 3ed096095a..8be68110f1 100644 --- a/src/Models/Unit.ts +++ b/src/Models/Unit.ts @@ -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, + tagRenderings: TagRenderingConfig[], ctx: string ): Unit[] { - if (!json.appliesToKey && !json.quantity) { - return this.loadFromLibrary(json, ctx) + + let types: Record = {} + for (const tagRendering of tagRenderings) { + if (tagRendering.freeform?.type) { + types[tagRendering.freeform.key] = tagRendering.freeform.type + } } - return [this.parse(json, ctx)] + + if (!json.appliesToKey && !json.quantity) { + return this.loadFromLibrary(json, types, ctx) + } + return this.parse(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, 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 { const m = new Map() - const units = (unit.units).map((json, i) => - this.parse(json, "unit.json.units." + i) + const units = (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, 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 } diff --git a/src/UI/InputElement/ValidatedInput.svelte b/src/UI/InputElement/ValidatedInput.svelte index 0ef857c64d..a13c355d36 100644 --- a/src/UI/InputElement/ValidatedInput.svelte +++ b/src/UI/InputElement/ValidatedInput.svelte @@ -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)) diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index a0dc35883b..831bd109a7 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -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