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