Themes(toilets): add an allowed range to some freeform inputs, allow to specify 'units' in the freeform, add the possibility to convert units

This commit is contained in:
Pieter Vander Vennet 2025-05-20 00:34:39 +02:00
parent 32cb8f489f
commit fb8ead2a2c
16 changed files with 270 additions and 103 deletions

View file

@ -79,7 +79,20 @@
], ],
"freeform": { "freeform": {
"key": "height", "key": "height",
"type": "pfloat" "type": "pfloat",
"unit": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
},
"range": {
"warnBelow": 0.8,
"warnAbove": 1.7,
"min": 0.4,
"max": 2
}
}, },
"render": { "render": {
"en": "The changing table is {canonical(height)} high", "en": "The changing table is {canonical(height)} high",
@ -104,7 +117,20 @@
}, },
"freeform": { "freeform": {
"key": "min_height", "key": "min_height",
"type": "pfloat" "type": "pfloat",
"unit": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
},
"range": {
"warnBelow": 0.8,
"warnAbove": 1.7,
"min": 0.4,
"max": 2
}
}, },
"render": { "render": {
"en": "The lowest height of the adult changing table is {canonical(min_height)}", "en": "The lowest height of the adult changing table is {canonical(min_height)}",
@ -134,7 +160,20 @@
}, },
"freeform": { "freeform": {
"key": "max_height", "key": "max_height",
"type": "pfloat" "type": "pfloat",
"unit": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
},
"range": {
"warnBelow": 0.8,
"warnAbove": 1.7,
"min": 0.4,
"max": 2
}
}, },
"render": { "render": {
"en": "The highest height of the adult changing table is {canonical(max_height)}", "en": "The highest height of the adult changing table is {canonical(max_height)}",
@ -226,34 +265,5 @@
"en": "Adult changing table", "en": "Adult changing table",
"nl": "Verzorgingstafel voor volwassenen", "nl": "Verzorgingstafel voor volwassenen",
"it": "Fasciatoio per adulti" "it": "Fasciatoio per adulti"
}, }
"units": [
{
"adult:height": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
}
},
{
"adult:min_height": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
}
},
{
"adult:max_height": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
}
}
]
} }

View file

@ -588,7 +588,20 @@
}, },
"freeform": { "freeform": {
"key": "width", "key": "width",
"type": "pfloat" "type": "pfloat",
"unit": {
"quantity": "distance",
"canonical": "m",
"denominations": [
"cm"
]
},
"range": {
"warnBelow": 0.8,
"warnAbove": 1.7,
"min": 0.4,
"max": 2
}
} }
}, },
{ {
@ -629,7 +642,18 @@
"es": "Altura del bordillo de la puerta", "es": "Altura del bordillo de la puerta",
"it": "Altezza del gradino della porta" "it": "Altezza del gradino della porta"
}, },
"type": "pfloat" "type": "pfloat",
"unit": {
"quantity": "distance",
"canonical": "m",
"denominations": [
"cm"
]
},
"range": {
"warnAbove": 0.25,
"max": 0.5
}
}, },
"mappings": [ "mappings": [
{ {
@ -720,23 +744,5 @@
"allowMove": { "allowMove": {
"enableImproveAccuracy": true, "enableImproveAccuracy": true,
"enableRelocation": false "enableRelocation": false
}, }
"units": [
{
"kerb:height": {
"quantity": "distance",
"canonical": "m",
"denominations": [
"cm"
]
},
"width": {
"quantity": "distance",
"canonical": "m",
"denominations": [
"cm"
]
}
}
]
} }

View file

@ -1534,7 +1534,7 @@
}, },
"render": { "render": {
"en": "The door to the wheelchair-accessible toilet is {canonical(door:width)} wide", "en": "The door to the wheelchair-accessible toilet is {canonical(door:width)} wide",
"nl": "De deur naar de rolstoeltoegankelijke toilet is {canonical(door:width)} wide", "nl": "De deur naar de rolstoeltoegankelijke toilet is {canonical(door:width)}",
"fr": "La porte des toilettes accessibles aux fauteuils roulants a une large de {canonical(door:width)}", "fr": "La porte des toilettes accessibles aux fauteuils roulants a une large de {canonical(door:width)}",
"de": "Die Tür zur rollstuhlgerechten Toilette ist {canonical(door:width)} breit", "de": "Die Tür zur rollstuhlgerechten Toilette ist {canonical(door:width)} breit",
"da": "Døren til det kørestolsvenlige toilet er {canonical(door:width)} bred", "da": "Døren til det kørestolsvenlige toilet er {canonical(door:width)} bred",
@ -1545,7 +1545,20 @@
}, },
"freeform": { "freeform": {
"key": "door:width", "key": "door:width",
"type": "pfloat" "type": "pfloat",
"unit": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
},
"range": {
"warnBelow": 0.6,
"min": 0.4,
"warnAbove": 2,
"max": 4
}
} }
}, },
{ {
@ -1734,16 +1747,5 @@
"allowMove": { "allowMove": {
"enableRelocation": false, "enableRelocation": false,
"enableImproveAccuracy": true "enableImproveAccuracy": true
}, }
"units": [
{
"door:width": {
"quantity": "distance",
"denominations": [
"m",
"cm"
]
}
}
]
} }

View file

@ -36,7 +36,8 @@
"da": "{quantity} Megawatt", "da": "{quantity} Megawatt",
"cs": "{quantity} megawatty", "cs": "{quantity} megawatty",
"es": "{quantity} megavatios" "es": "{quantity} megavatios"
} },
"factorToCanonical": 1000000
}, },
{ {
"canonicalDenomination": "kW", "canonicalDenomination": "kW",
@ -60,7 +61,8 @@
"da": "{quantity} Kilowatt", "da": "{quantity} Kilowatt",
"cs": "{quantity} kilowatty", "cs": "{quantity} kilowatty",
"es": "{quantity} kilovatios" "es": "{quantity} kilovatios"
} },
"factorToCanonical": 1000
}, },
{ {
"canonicalDenomination": "W", "canonicalDenomination": "W",
@ -106,7 +108,8 @@
"cs": "{quantity} gigawatty", "cs": "{quantity} gigawatty",
"zh_Hant": "{quantity} 千兆瓦", "zh_Hant": "{quantity} 千兆瓦",
"es": "{quantity} gigavatios" "es": "{quantity} gigavatios"
} },
"factorToCanonical": 1000000000
} }
], ],
"eraseInvalidValues": true "eraseInvalidValues": true
@ -231,7 +234,8 @@
"hu": "egy centiméter", "hu": "egy centiméter",
"es": "un centímetro", "es": "un centímetro",
"it": "one centimeter" "it": "one centimeter"
} },
"factorToCanonical": 0.01
}, },
{ {
"canonicalDenomination": "mm", "canonicalDenomination": "mm",
@ -259,7 +263,8 @@
"hu": "egy milliméter", "hu": "egy milliméter",
"es": "un milímetro", "es": "un milímetro",
"it": "one millimeter" "it": "one millimeter"
} },
"factorToCanonical": 0.001
}, },
{ {
"canonicalDenomination": "ft", "canonicalDenomination": "ft",
@ -283,7 +288,8 @@
"nb_NO": "{quantity} fot", "nb_NO": "{quantity} fot",
"pa_PK": "{quantity} ؜ فوٹ", "pa_PK": "{quantity} ؜ فوٹ",
"hu": "{quantity} láb" "hu": "{quantity} láb"
} },
"factorToCanonical": 0.3048
} }
] ]
}, },

View file

@ -858,6 +858,12 @@
"description": "a number", "description": "a number",
"feedback": "This is not a number" "feedback": "This is not a number"
}, },
"generic": {
"suspiciouslyHigh": "This value is suspiciously high. Are you sure it is correct?",
"suspiciouslyLow": "This value is suspiciously low. Are you sure it is correct?",
"tooHigh": "This value is too high - the highest allowed value is {max}",
"tooLow": "This value is too low - the lowest allowed value is {min}"
},
"id": { "id": {
"description": "an identifier", "description": "an identifier",
"invalidCharacter": "An id can only contain letters, digits and underscores", "invalidCharacter": "An id can only contain letters, digits and underscores",

View file

@ -280,11 +280,19 @@ export class GenerateDocs extends Script {
"Units ", "Units ",
"## " + layer.id, "## " + layer.id,
] ]
for (const unit of layer.units) { for (const unit of layer.units) {
els.push("### " + unit.quantity) els.push("### " + unit.quantity)
const defaultUnit = unit.getDefaultDenomination(() => undefined)
for (const denomination of unit.denominations) { for (const denomination of unit.denominations) {
els.push("#### " + denomination.canonical) els.push("#### " + denomination.canonical)
if (denomination.validator) {
els.push(`Validator is *${denomination.validator.name}*`)
}
if (denomination.factorToCanonical) {
els.push(`1${denomination.canonical} = ${denomination.factorToCanonical}${defaultUnit.canonical}`)
}
if (denomination.useIfNoUnitGiven === true) { if (denomination.useIfNoUnitGiven === true) {
els.push("*Default denomination*") els.push("*Default denomination*")
} else if ( } else if (

View file

@ -16,7 +16,12 @@ 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 public readonly validator: Validator
/**
* IF a conversion to the canonical value is possible, this is the factor.
* E.g. for "cm", the factor is 0.01, as "1cm = 0.01m"
*/
public readonly factorToCanonical?: number
private constructor( private constructor(
canonical: string, canonical: string,
@ -27,7 +32,8 @@ export class Denomination {
alternativeDenominations: string[], alternativeDenominations: string[],
_human: TypedTranslation<{ quantity: string }>, _human: TypedTranslation<{ quantity: string }>,
_humanSingular: Translation, _humanSingular: Translation,
validator: Validator validator: Validator,
factorToCanonical: number
) { ) {
this.canonical = canonical this.canonical = canonical
this._canonicalSingular = _canonicalSingular this._canonicalSingular = _canonicalSingular
@ -37,7 +43,8 @@ export class Denomination {
this.alternativeDenominations = alternativeDenominations this.alternativeDenominations = alternativeDenominations
this.human = _human this.human = _human
this.humanSingular = _humanSingular this.humanSingular = _humanSingular
this._validator = validator this.validator = validator
this.factorToCanonical = factorToCanonical
} }
public static fromJson(json: DenominationConfigJson, validator: Validator, context: string) { public static fromJson(json: DenominationConfigJson, validator: Validator, context: string) {
@ -73,7 +80,8 @@ export class Denomination {
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 validator,
json.factorToCanonical
) )
} }
@ -87,7 +95,8 @@ export class Denomination {
this.alternativeDenominations, this.alternativeDenominations,
this.human, this.human,
this.humanSingular, this.humanSingular,
this._validator this.validator,
this.factorToCanonical
) )
} }
@ -101,7 +110,8 @@ export class Denomination {
[this.canonical, ...this.alternativeDenominations], [this.canonical, ...this.alternativeDenominations],
this.human, this.human,
this.humanSingular, this.humanSingular,
this._validator this.validator,
this.factorToCanonical
) )
} }
@ -217,10 +227,10 @@ export class Denomination {
return null return null
} }
if (!this._validator.isValid(value.trim())) { if (!this.validator.isValid(value.trim())) {
return null return null
} }
return this._validator.reformat(value.trim()) return this.validator.reformat(value.trim())
} }
withValidator(validator: Validator) { withValidator(validator: Validator) {
@ -233,7 +243,8 @@ export class Denomination {
this.alternativeDenominations, this.alternativeDenominations,
this.human, this.human,
this.humanSingular, this.humanSingular,
validator validator,
this.factorToCanonical
) )
} }
} }

View file

@ -962,6 +962,7 @@ class MoveUnitConfigs extends DesugaringStep<LayerConfigJson> {
json.units.push({ json.units.push({
[qtr.freeform.key]: unitConfig [qtr.freeform.key]: unitConfig
}) })
// Note: we do not delete the config - this way, if the tagRendering is imported in another layer, the unit comes along
} }
return json return json
} }
@ -1052,6 +1053,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
), ),
new AddFiltersFromTagRenderings(), new AddFiltersFromTagRenderings(),
new ExpandFilter(state), new ExpandFilter(state),
new MoveUnitConfigs(),
new PruneFilters() new PruneFilters()
) )
} }

View file

@ -312,6 +312,16 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
canonical?: string canonical?: string
inverted?: boolean inverted?: boolean
},
/**
* question: In what range should the value be?
* For example, a door width under 65cm is suspicious, under 40cm it is a mistake.
*/
range?: {
min?: number,
warnBelow?: number,
warnAbove?: number
max?: number
} }
} }

View file

@ -85,7 +85,8 @@ export default interface UnitConfigJson {
* When a default input method should be used, this can be specified by setting the canonical denomination here, e.g. * When a default input method should be used, this can be specified by setting the canonical denomination here, e.g.
* `defaultInput: "cm"`. This must be a denomination which appears in the applicableUnits * `defaultInput: "cm"`. This must be a denomination which appears in the applicableUnits
*/ */
defaultInput?: string defaultInput?: string,
} }
export interface DenominationConfigJson { export interface DenominationConfigJson {
@ -154,4 +155,9 @@ export interface DenominationConfigJson {
* E.g.: `50 mph` instad of `50mph` * E.g.: `50 mph` instad of `50mph`
*/ */
addSpace?: boolean addSpace?: boolean
/**
* If the canonical unit (e.g. 1m) is multiplied with the factorToCanonical (0.01) you will get the current unit (1cm)
*/
factorToCanonical?: number
} }

View file

@ -5,10 +5,7 @@ import { TagUtils } from "../../Logic/Tags/TagUtils"
import { And } from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
import { import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators" import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import { RegexTag } from "../../Logic/Tags/RegexTag" import { RegexTag } from "../../Logic/Tags/RegexTag"
@ -20,6 +17,7 @@ import MarkdownUtils from "../../Utils/MarkdownUtils"
import { UploadableTag } from "../../Logic/Tags/TagTypes" import { UploadableTag } from "../../Logic/Tags/TagTypes"
import LayerConfig from "./LayerConfig" import LayerConfig from "./LayerConfig"
import ComparingTag from "../../Logic/Tags/ComparingTag" import ComparingTag from "../../Logic/Tags/ComparingTag"
import { Unit } from "../Unit"
export interface Mapping { export interface Mapping {
readonly if: UploadableTag readonly if: UploadableTag
@ -41,6 +39,12 @@ export interface Mapping {
readonly priorityIf?: TagsFilter readonly priorityIf?: TagsFilter
} }
export interface ValueRange {
min?: number,
max?: number,
warnBelow?: number,
warnAbove?: number
}
/*** /***
* The parsed version of TagRenderingConfigJSON * The parsed version of TagRenderingConfigJSON
* Identical data, but with some methods and validation * Identical data, but with some methods and validation
@ -79,6 +83,7 @@ export default class TagRenderingConfig {
readonly default?: string readonly default?: string
readonly postfixDistinguished?: string readonly postfixDistinguished?: string
readonly args?: any readonly args?: any
readonly range: ValueRange
} }
public readonly multiAnswer: boolean public readonly multiAnswer: boolean
@ -233,6 +238,7 @@ export default class TagRenderingConfig {
default: json.freeform.default, default: json.freeform.default,
postfixDistinguished: json.freeform.postfixDistinguished?.trim(), postfixDistinguished: json.freeform.postfixDistinguished?.trim(),
args: json.freeform.helperArgs, args: json.freeform.helperArgs,
range: json.freeform.range
} }
if (json.freeform["extraTags"] !== undefined) { if (json.freeform["extraTags"] !== undefined) {
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})` throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
@ -787,7 +793,8 @@ export default class TagRenderingConfig {
freeformValue: string | undefined, freeformValue: string | undefined,
singleSelectedMapping: number, singleSelectedMapping: number,
multiSelectedMapping: boolean[] | undefined, multiSelectedMapping: boolean[] | undefined,
currentProperties: Record<string, string> currentProperties: Record<string, string>,
unit?: Unit
): UploadableTag[] { ): UploadableTag[] {
if (typeof freeformValue === "string") { if (typeof freeformValue === "string") {
freeformValue = freeformValue?.trim() freeformValue = freeformValue?.trim()
@ -795,7 +802,14 @@ export default class TagRenderingConfig {
const validator = Validators.get(<ValidatorType>this.freeform?.type) const validator = Validators.get(<ValidatorType>this.freeform?.type)
if (validator && freeformValue) { if (validator && freeformValue) {
freeformValue = validator.reformat(freeformValue, () => currentProperties["_country"]) // We try to reformat; but a unit might annoy us here
if (unit) {
const [valueNoUnit, denom] = unit.findDenomination(freeformValue, () => currentProperties["_country"])
const formatted = validator.reformat(valueNoUnit, () => currentProperties["_country"])
freeformValue = formatted + denom.canonical
} else {
freeformValue = validator.reformat(freeformValue, () => currentProperties["_country"])
}
} }
if (freeformValue === "") { if (freeformValue === "") {
freeformValue = undefined freeformValue = undefined
@ -918,9 +932,23 @@ export default class TagRenderingConfig {
GenerateDocumentation(lang: string = "en"): string { GenerateDocumentation(lang: string = "en"): string {
let freeform: string = undefined let freeform: string = undefined
if (this.render) { if (this.render) {
freeform = "*" + this.render.textFor(lang) + "*" freeform = "\n*" + this.render.textFor(lang) + "*"
if (this.freeform?.key) { if (this.freeform?.key) {
freeform += " is shown if `" + this.freeform.key + "` is set" freeform += " is shown if `" + this.freeform.key + "` is set."
}
if (this.question && this.freeform.range) {
freeform += "\n\nThe allowed input is of type " + (this.freeform.type ?? "string")
if (this.freeform.range) {
const r = this.freeform.range
freeform += ` and is in range ${r.min ?? "-infinty"} until ${r.max ?? "infinity"} (both inclusive).`
if (r.warnAbove && r.warnBelow) {
freeform += ` A warning will appear if the value is outside of ${r.warnBelow} and ${r.warnAbove}.`
} else if (r.warnBelow) {
freeform += ` A warning will appear below ${r.warnBelow}.`
} else if (r.warnAbove) {
freeform += ` A warning will appear above ${r.warnAbove}.`
}
}
} }
} }

View file

@ -5,6 +5,7 @@ import unit from "../../assets/layers/unit/unit.json"
import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig"
import Validators, { ValidatorType } from "../UI/InputElement/Validators" import Validators, { ValidatorType } from "../UI/InputElement/Validators"
import { Validator } from "../UI/InputElement/Validator" import { Validator } from "../UI/InputElement/Validator"
import FloatValidator from "../UI/InputElement/Validators/FloatValidator"
export class Unit { export class Unit {
private static allUnits = this.initUnits() private static allUnits = this.initUnits()
@ -334,10 +335,11 @@ export class Unit {
return [undefined, undefined] return [undefined, undefined]
} }
asHumanLongValue(value: string, country: () => string): BaseUIElement | string { asHumanLongValue(value: string | number, country: () => string): BaseUIElement | string {
if (value === undefined) { if (value === undefined) {
return undefined return undefined
} }
value = "" + value
const [stripped, denom] = this.findDenomination(value, country) const [stripped, denom] = this.findDenomination(value, country)
const human = denom?.human const human = denom?.human
if (this.inverted) { if (this.inverted) {
@ -393,4 +395,19 @@ export class Unit {
} }
return this.denominations[0] return this.denominations[0]
} }
/**
* Gets the value in the canonical denomination;
* e.g. "1cm -> 0.01" as it is 0.01meter
* @param v
*/
public valueInCanonical(value: string, country: () => string): number {
const denom = this.findDenomination(value, country)
if (!denom) {
return undefined
}
const [v, d] = denom
const vf = new FloatValidator().reformat(v)
return Number(vf) * (d.factorToCanonical ?? 1)
}
} }

View file

@ -4,12 +4,17 @@
import Validators from "./Validators" import Validators from "./Validators"
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid" import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import { createEventDispatcher, onDestroy } from "svelte" import { createEventDispatcher, onDestroy } from "svelte"
import { Validator } from "./Validator" import { Validator } from "./Validator"
import { Unit } from "../../Models/Unit" import { Unit } from "../../Models/Unit"
import UnitInput from "../Popup/UnitInput.svelte" import UnitInput from "../Popup/UnitInput.svelte"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge"
import type { ValueRange } from "../../Models/ThemeConfig/TagRenderingConfig"
import Translations from "../i18n/Translations"
import FloatValidator from "./Validators/FloatValidator"
import BaseUIElement from "../BaseUIElement"
export let type: ValidatorType export let type: ValidatorType
export let feedback: UIEventSource<Translation> | undefined = undefined export let feedback: UIEventSource<Translation> | undefined = undefined
@ -18,6 +23,7 @@
export let placeholder: string | Translation | undefined = undefined export let placeholder: string | Translation | undefined = undefined
export let autofocus: boolean = false export let autofocus: boolean = false
export let unit: Unit = undefined export let unit: Unit = undefined
export let range: ValueRange = undefined
/** /**
* Valid state, exported to the calling component * Valid state, exported to the calling component
*/ */
@ -42,7 +48,7 @@
function initValueAndDenom() { function initValueAndDenom() {
if (unit && value.data) { if (unit && value.data) {
const [v, denom] = unit?.findDenomination(value.data, getCountry) const [v, denom] = unit.findDenomination(value.data, getCountry)
if (denom) { if (denom) {
unvalidatedText.setData(v) unvalidatedText.setData(v)
selectedUnit.setData(denom.canonical) selectedUnit.setData(denom.canonical)
@ -62,7 +68,6 @@
} }
} }
initValueAndDenom() initValueAndDenom()
$: { $: {
// The type changed -> reset some values // The type changed -> reset some values
validator = Validators.get(type ?? "string") validator = Validators.get(type ?? "string")
@ -77,6 +82,41 @@
initValueAndDenom() initValueAndDenom()
} }
const t = Translations.t.validation.generic
/**
* Side effect: sets the feedback, returns true/false if valid
* @param canonicalValue
*/
function validateRange(canonicalValue: number): boolean {
if (!range) {
return true
}
if (canonicalValue < range.warnBelow) {
feedback.set(t.suspiciouslyLow)
}
if (canonicalValue > range.warnAbove) {
feedback.set(t.suspiciouslyHigh)
}
if (canonicalValue > range.max) {
let max: number | string | BaseUIElement = range.max
if (unit) {
max = unit.asHumanLongValue(max)
}
feedback.set(t.tooHigh.Subs({ max }))
return false
}
if (canonicalValue < range.min) {
let min: number | string | BaseUIElement = range.min
if (unit) {
min = unit.asHumanLongValue(min)
}
feedback.set(t.tooLow.Subs({ min }))
return false
}
return true
}
function setValues() { function setValues() {
// Update the value stores // Update the value stores
const v = unvalidatedText.data const v = unvalidatedText.data
@ -92,13 +132,22 @@
} }
if (selectedUnit.data) { if (selectedUnit.data) {
value.setData(unit.toOsm(v, selectedUnit.data)) const canonicalValue = unit.valueInCanonical(v + selectedUnit.data)
if (validateRange(canonicalValue)) {
value.setData(unit.toOsm(v, selectedUnit.data))
} else {
value.set(undefined)
}
} else { } else {
value.setData(v) if (validateRange(v)) {
value.setData(v)
} else {
value.set(undefined)
}
} }
} }
onDestroy(unvalidatedText.addCallbackAndRun((_) => setValues())) onDestroy(unvalidatedText.addCallbackAndRun(() => setValues()))
if (unit === undefined) { if (unit === undefined) {
onDestroy( onDestroy(
value.addCallbackAndRunD((fromUpstream) => { value.addCallbackAndRunD((fromUpstream) => {
@ -110,7 +159,7 @@
} else { } else {
// Handled by the UnitInput // Handled by the UnitInput
} }
onDestroy(selectedUnit.addCallback((_) => setValues())) onDestroy(selectedUnit.addCallback(() => setValues()))
if (validator === undefined) { if (validator === undefined) {
throw ( throw (
"Not a valid type (no validator found) for type '" + "Not a valid type (no validator found) for type '" +

View file

@ -17,7 +17,6 @@ export default class FloatValidator extends Validator {
* new FloatValidator().isValid("0,2") // => true * new FloatValidator().isValid("0,2") // => true
*/ */
isValid(str: string) { isValid(str: string) {
console.log("Is valid?", str, FloatValidator.formattingHasComma)
if (!FloatValidator.formattingHasComma) { if (!FloatValidator.formattingHasComma) {
str = str.replace(",", ".") str = str.replace(",", ".")
} }
@ -28,7 +27,11 @@ export default class FloatValidator extends Validator {
if (!FloatValidator.formattingHasComma) { if (!FloatValidator.formattingHasComma) {
str = str.replace(",", ".") str = str.replace(",", ".")
} }
return "" + Number(str) let formatted = "" + Number(str)
if (str.startsWith("0") && str.length > 1 && str.indexOf(".") < 0) {
formatted = "0" + formatted
}
return formatted
} }
getFeedback(s: string): Translation { getFeedback(s: string): Translation {

View file

@ -53,6 +53,7 @@
type={config.freeform.type} type={config.freeform.type}
{placeholder} {placeholder}
{value} {value}
range={config.freeform.range}
/> />
</Inline> </Inline>
{:else if InputHelpers.hideInputField.indexOf(config.freeform.type) < 0} {:else if InputHelpers.hideInputField.indexOf(config.freeform.type) < 0}
@ -66,6 +67,7 @@
{placeholder} {placeholder}
{value} {value}
{unvalidatedText} {unvalidatedText}
range={config.freeform.range}
/> />
{/if} {/if}

View file

@ -221,7 +221,8 @@
$freeformInput, $freeformInput,
selectedMapping, selectedMapping,
checkedMappings, checkedMappings,
tags.data tags.data,
unit
) )
if (featureSwitchIsDebugging?.data) { if (featureSwitchIsDebugging?.data) {
console.log( console.log(