forked from MapComplete/MapComplete
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:
parent
32cb8f489f
commit
fb8ead2a2c
16 changed files with 270 additions and 103 deletions
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}.`
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 '" +
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue