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 e71a80732e
commit 1e0ac3febf
16 changed files with 270 additions and 103 deletions

View file

@ -16,7 +16,12 @@ export class Denomination {
public readonly alternativeDenominations: string[]
public readonly human: TypedTranslation<{ quantity: string }>
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(
canonical: string,
@ -27,7 +32,8 @@ export class Denomination {
alternativeDenominations: string[],
_human: TypedTranslation<{ quantity: string }>,
_humanSingular: Translation,
validator: Validator
validator: Validator,
factorToCanonical: number
) {
this.canonical = canonical
this._canonicalSingular = _canonicalSingular
@ -37,7 +43,8 @@ export class Denomination {
this.alternativeDenominations = alternativeDenominations
this.human = _human
this.humanSingular = _humanSingular
this._validator = validator
this.validator = validator
this.factorToCanonical = factorToCanonical
}
public static fromJson(json: DenominationConfigJson, validator: Validator, context: string) {
@ -73,7 +80,8 @@ export class Denomination {
json.alternativeDenomination?.map((v) => v.trim()) ?? [],
humanTexts,
Translations.T(json.humanSingular, context + "humanSingular"),
validator
validator,
json.factorToCanonical
)
}
@ -87,7 +95,8 @@ export class Denomination {
this.alternativeDenominations,
this.human,
this.humanSingular,
this._validator
this.validator,
this.factorToCanonical
)
}
@ -101,7 +110,8 @@ export class Denomination {
[this.canonical, ...this.alternativeDenominations],
this.human,
this.humanSingular,
this._validator
this.validator,
this.factorToCanonical
)
}
@ -217,10 +227,10 @@ export class Denomination {
return null
}
if (!this._validator.isValid(value.trim())) {
if (!this.validator.isValid(value.trim())) {
return null
}
return this._validator.reformat(value.trim())
return this.validator.reformat(value.trim())
}
withValidator(validator: Validator) {
@ -233,7 +243,8 @@ export class Denomination {
this.alternativeDenominations,
this.human,
this.humanSingular,
validator
validator,
this.factorToCanonical
)
}
}

View file

@ -962,6 +962,7 @@ class MoveUnitConfigs extends DesugaringStep<LayerConfigJson> {
json.units.push({
[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
}
@ -1052,6 +1053,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
),
new AddFiltersFromTagRenderings(),
new ExpandFilter(state),
new MoveUnitConfigs(),
new PruneFilters()
)
}

View file

@ -312,6 +312,16 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
canonical?: string
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.
* `defaultInput: "cm"`. This must be a denomination which appears in the applicableUnits
*/
defaultInput?: string
defaultInput?: string,
}
export interface DenominationConfigJson {
@ -154,4 +155,9 @@ export interface DenominationConfigJson {
* E.g.: `50 mph` instad of `50mph`
*/
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 { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag"
import {
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import { RegexTag } from "../../Logic/Tags/RegexTag"
@ -20,6 +17,7 @@ import MarkdownUtils from "../../Utils/MarkdownUtils"
import { UploadableTag } from "../../Logic/Tags/TagTypes"
import LayerConfig from "./LayerConfig"
import ComparingTag from "../../Logic/Tags/ComparingTag"
import { Unit } from "../Unit"
export interface Mapping {
readonly if: UploadableTag
@ -41,6 +39,12 @@ export interface Mapping {
readonly priorityIf?: TagsFilter
}
export interface ValueRange {
min?: number,
max?: number,
warnBelow?: number,
warnAbove?: number
}
/***
* The parsed version of TagRenderingConfigJSON
* Identical data, but with some methods and validation
@ -79,6 +83,7 @@ export default class TagRenderingConfig {
readonly default?: string
readonly postfixDistinguished?: string
readonly args?: any
readonly range: ValueRange
}
public readonly multiAnswer: boolean
@ -233,6 +238,7 @@ export default class TagRenderingConfig {
default: json.freeform.default,
postfixDistinguished: json.freeform.postfixDistinguished?.trim(),
args: json.freeform.helperArgs,
range: json.freeform.range
}
if (json.freeform["extraTags"] !== undefined) {
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
@ -787,7 +793,8 @@ export default class TagRenderingConfig {
freeformValue: string | undefined,
singleSelectedMapping: number,
multiSelectedMapping: boolean[] | undefined,
currentProperties: Record<string, string>
currentProperties: Record<string, string>,
unit?: Unit
): UploadableTag[] {
if (typeof freeformValue === "string") {
freeformValue = freeformValue?.trim()
@ -795,7 +802,14 @@ export default class TagRenderingConfig {
const validator = Validators.get(<ValidatorType>this.freeform?.type)
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 === "") {
freeformValue = undefined
@ -918,9 +932,23 @@ export default class TagRenderingConfig {
GenerateDocumentation(lang: string = "en"): string {
let freeform: string = undefined
if (this.render) {
freeform = "*" + this.render.textFor(lang) + "*"
freeform = "\n*" + this.render.textFor(lang) + "*"
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 Validators, { ValidatorType } from "../UI/InputElement/Validators"
import { Validator } from "../UI/InputElement/Validator"
import FloatValidator from "../UI/InputElement/Validators/FloatValidator"
export class Unit {
private static allUnits = this.initUnits()
@ -334,10 +335,11 @@ export class Unit {
return [undefined, undefined]
}
asHumanLongValue(value: string, country: () => string): BaseUIElement | string {
asHumanLongValue(value: string | number, country: () => string): BaseUIElement | string {
if (value === undefined) {
return undefined
}
value = "" + value
const [stripped, denom] = this.findDenomination(value, country)
const human = denom?.human
if (this.inverted) {
@ -393,4 +395,19 @@ export class Unit {
}
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)
}
}