From 7c42758b42c48d83239325235b9a4d4e7967f8b9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 28 Jul 2025 03:14:33 +0200 Subject: [PATCH] Refactoring: dismantle 'inputHelpers' --- android | 2 +- assets/svg/direction_stroke.svg | 66 ++++- public/css/index-tailwind-output.css | 4 + src/Models/ThemeConfig/LayerConfig.ts | 3 +- src/Models/Unit.ts | 224 ----------------- src/Models/UnitUtils.ts | 232 ++++++++++++++++++ .../Helpers/DirectionInput.svelte | 33 ++- src/UI/InputElement/InputHelper.svelte | 60 +---- src/UI/InputElement/InputHelpers.ts | 67 ----- src/UI/InputElement/Validator.ts | 4 + src/UI/InputElement/Validators.ts | 153 ++++++------ .../Validators/CollectionTimesValidator.ts | 5 + .../InputElement/Validators/ColorValidator.ts | 4 + .../InputElement/Validators/DateValidator.ts | 5 + .../Validators/DirectionValidator.ts | 3 + .../Validators/DistanceValidator.ts | 2 + .../Validators/ImageUrlValidator.ts | 4 + .../Validators/OpeningHoursValidator.ts | 7 + .../Validators/SimpleTagValidator.ts | 5 + .../InputElement/Validators/SlopeValidator.ts | 4 + .../InputElement/Validators/TagValidator.ts | 4 + .../InputElement/Validators/TimeValidator.ts | 6 +- .../Validators/TranslationValidator.ts | 4 + .../Validators/WikidataValidator.ts | 3 + .../Popup/TagRendering/FreeformInput.svelte | 18 +- src/assets/svg/Direction_stroke.svelte | 2 +- 26 files changed, 485 insertions(+), 439 deletions(-) create mode 100644 src/Models/UnitUtils.ts delete mode 100644 src/UI/InputElement/InputHelpers.ts diff --git a/android b/android index b7b29d20e..a48aaffec 160000 --- a/android +++ b/android @@ -1 +1 @@ -Subproject commit b7b29d20e40bde9144c719a2b59484c04cc79b9f +Subproject commit a48aaffec4ca59a2129834207e72ee3df85d2cd6 diff --git a/assets/svg/direction_stroke.svg b/assets/svg/direction_stroke.svg index 521b40b0d..43a7a145f 100644 --- a/assets/svg/direction_stroke.svg +++ b/assets/svg/direction_stroke.svg @@ -1,9 +1,59 @@ - - - - - - - + + + + + + + + + - \ No newline at end of file + diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 56c5498b4..6d641cbe3 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -2209,6 +2209,10 @@ input[type="range"].range-lg::-moz-range-thumb { min-width: 8rem; } +.min-w-48 { + min-width: 12rem; +} + .min-w-6 { min-width: 1.5rem; } diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index 6ca6f23eb..5ad4babfa 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -22,6 +22,7 @@ import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRender import MarkdownUtils from "../../Utils/MarkdownUtils" import { And } from "../../Logic/Tags/And" import OsmWiki from "../../Logic/Osm/OsmWiki" +import { UnitUtils } from "../UnitUtils" export default class LayerConfig extends WithContextLoader { public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const @@ -312,7 +313,7 @@ export default class LayerConfig extends WithContextLoader { ) } this.units = (json.units ?? []).flatMap((unitJson, i) => - Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`) + UnitUtils.fromJson()(unitJson, this.tagRenderings, `${context}.unit[${i}]`) ) { let filter = json.filter diff --git a/src/Models/Unit.ts b/src/Models/Unit.ts index 311e66133..75c8a7adb 100644 --- a/src/Models/Unit.ts +++ b/src/Models/Unit.ts @@ -1,14 +1,9 @@ import BaseUIElement from "../UI/BaseUIElement" import { Denomination } from "./Denomination" -import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson" -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() public readonly appliesToKeys: Set public readonly denominations: Denomination[] public readonly denominationsSorted: Denomination[] @@ -85,226 +80,7 @@ export class Unit { } } - static fromJson( - json: - | UnitConfigJson - | Record< - string, - string | { quantity: string; denominations: string[]; inverted?: boolean } - >, - tagRenderings: TagRenderingConfig[], - ctx: string - ): Unit[] { - const types: Record = {} - for (const tagRendering of tagRenderings) { - if (tagRendering.freeform?.type) { - types[tagRendering.freeform.key] = tagRendering.freeform.type - } - } - 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 - ) - } - - /** - * - * // Should detect invalid defaultInput - * let threwError = false - * try{ - * Unit.parse({ - * appliesToKey: ["length"], - * defaultInput: "xcm", - * applicableUnits: [ - * { - * canonicalDenomination: "m", - * useIfNoUnitGiven: true, - * human: "meter" - * } - * ] - * },"test") - * }catch(e){ - * threwError = true - * } - * threwError // => true - * - * // Should work - * Unit.parse({ - * appliesToKey: ["length"], - * defaultInput: "xcm", - * applicableUnits: [ - * { - * canonicalDenomination: "m", - * useIfNoUnitGiven: true, - * humen: "meter" - * }, - * { - * canonicalDenomination: "cm", - * human: "centimeter" - * } - * ] - * }, "test") - */ - private static parse( - json: UnitConfigJson, - types: Record, - ctx: string - ): Unit[] { - const appliesTo = json.appliesToKey - for (let i = 0; i < (appliesTo ?? []).length; i++) { - const key = appliesTo[i] - if (key.trim() !== key) { - throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace` - } - } - - if ((json.applicableUnits ?? []).length === 0) { - throw `${ctx}: define at least one applicable unit` - } - // Some keys do have unit handling - - const units: Unit[] = [] - if (appliesTo === undefined) { - units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx)) - } - 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).flatMap((json, i) => - this.parse(json, {}, "unit.json.units." + i) - ) - - for (const unit of units) { - m.set(unit.quantity, unit) - } - return m - } - - private static getFromLibrary(name: string, ctx: string): Unit { - const loaded = this.allUnits.get(name) - if (loaded === undefined) { - throw ( - "No unit with quantity name " + - name + - " found (at " + - ctx + - "). Try one of: " + - Array.from(this.allUnits.keys()).join(", ") - ) - } - return loaded - } - - private static loadFromLibrary( - spec: Record< - string, - | string - | { quantity: string; denominations: string[]; canonical?: string; inverted?: boolean } - >, - 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, - validator, - toLoad["inverted"] - ) - ) - continue - } - - const loaded = this.getFromLibrary(toLoad.quantity, ctx) - const quantity = toLoad.quantity - - const fetchDenom = (d: string): Denomination => { - const found = loaded.denominations.find( - (denom) => denom.canonical.toLowerCase() === d - ) - if (!found) { - throw ( - `Could not find a denomination \`${d}\`for quantity ${quantity} at ${ctx}. Perhaps you meant to use on of ` + - loaded.denominations.map((d) => d.canonical).join(", ") - ) - } - return found - } - - if (!Array.isArray(toLoad.denominations)) { - throw ( - "toLoad is not an array. Did you forget the [ and ] around the denominations at " + - ctx + - "?" - ) - } - 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).withValidator(validator) - denoms.unshift(canonical.withBlankCanonical()) - } - units.push( - new Unit( - loaded.quantity, - [key], - denoms, - loaded.eraseInvalid, - validator, - toLoad["inverted"] - ) - ) - } - return units - } isApplicableToKey(key: string | undefined): boolean { if (key === undefined) { diff --git a/src/Models/UnitUtils.ts b/src/Models/UnitUtils.ts new file mode 100644 index 000000000..31c1f90d2 --- /dev/null +++ b/src/Models/UnitUtils.ts @@ -0,0 +1,232 @@ +import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson" +import TagRenderingConfig from "./ThemeConfig/TagRenderingConfig" +import Validators, { ValidatorType } from "../UI/InputElement/Validators" +import { Validator } from "../UI/InputElement/Validator" +import { Denomination } from "./Denomination" +import unit from "../../assets/layers/unit/unit.json" +import { Unit } from "./Unit" + +export class UnitUtils { + private static allUnits = this.initUnits() + + static fromJson( + json: + | UnitConfigJson + | Record< + string, + string | { quantity: string; denominations: string[]; inverted?: boolean } + >, + tagRenderings: TagRenderingConfig[], + ctx: string, + ): Unit[] { + const types: Record = {} + for (const tagRendering of tagRenderings) { + if (tagRendering.freeform?.type) { + types[tagRendering.freeform.key] = tagRendering.freeform.type + } + } + + 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, + ) + } + + /** + * + * // Should detect invalid defaultInput + * let threwError = false + * try{ + * Unit.parse({ + * appliesToKey: ["length"], + * defaultInput: "xcm", + * applicableUnits: [ + * { + * canonicalDenomination: "m", + * useIfNoUnitGiven: true, + * human: "meter" + * } + * ] + * },"test") + * }catch(e){ + * threwError = true + * } + * threwError // => true + * + * // Should work + * Unit.parse({ + * appliesToKey: ["length"], + * defaultInput: "xcm", + * applicableUnits: [ + * { + * canonicalDenomination: "m", + * useIfNoUnitGiven: true, + * humen: "meter" + * }, + * { + * canonicalDenomination: "cm", + * human: "centimeter" + * } + * ] + * }, "test") + */ + private static parse( + json: UnitConfigJson, + types: Record, + ctx: string, + ): Unit[] { + const appliesTo = json.appliesToKey + for (let i = 0; i < (appliesTo ?? []).length; i++) { + const key = appliesTo[i] + if (key.trim() !== key) { + throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace` + } + } + + if ((json.applicableUnits ?? []).length === 0) { + throw `${ctx}: define at least one applicable unit` + } + // Some keys do have unit handling + + const units: Unit[] = [] + if (appliesTo === undefined) { + units.push(this.parseDenomination(json, Validators.get("float"), undefined, ctx)) + } + 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).flatMap((json, i) => + this.parse(json, {}, "unit.json.units." + i), + ) + + for (const unit of units) { + m.set(unit.quantity, unit) + } + return m + } + + private static getFromLibrary(name: string, ctx: string): Unit { + const loaded = this.allUnits.get(name) + if (loaded === undefined) { + throw ( + "No unit with quantity name " + + name + + " found (at " + + ctx + + "). Try one of: " + + Array.from(this.allUnits.keys()).join(", ") + ) + } + return loaded + } + + private static loadFromLibrary( + spec: Record< + string, + | string + | { quantity: string; denominations: string[]; canonical?: string; inverted?: boolean } + >, + 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, + validator, + toLoad["inverted"], + ), + ) + continue + } + + const loaded = this.getFromLibrary(toLoad.quantity, ctx) + const quantity = toLoad.quantity + + const fetchDenom = (d: string): Denomination => { + const found = loaded.denominations.find( + (denom) => denom.canonical.toLowerCase() === d, + ) + if (!found) { + throw ( + `Could not find a denomination \`${d}\`for quantity ${quantity} at ${ctx}. Perhaps you meant to use on of ` + + loaded.denominations.map((d) => d.canonical).join(", ") + ) + } + return found + } + + if (!Array.isArray(toLoad.denominations)) { + throw ( + "toLoad is not an array. Did you forget the [ and ] around the denominations at " + + ctx + + "?" + ) + } + 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).withValidator(validator) + denoms.unshift(canonical.withBlankCanonical()) + } + units.push( + new Unit( + loaded.quantity, + [key], + denoms, + loaded.eraseInvalid, + validator, + toLoad["inverted"], + ), + ) + } + return units + } +} diff --git a/src/UI/InputElement/Helpers/DirectionInput.svelte b/src/UI/InputElement/Helpers/DirectionInput.svelte index 860fc1ba3..691f27b6c 100644 --- a/src/UI/InputElement/Helpers/DirectionInput.svelte +++ b/src/UI/InputElement/Helpers/DirectionInput.svelte @@ -6,25 +6,35 @@ import MaplibreMap from "../../Map/MaplibreMap.svelte" import Direction_stroke from "../../../assets/svg/Direction_stroke.svelte" import type { SpecialVisualizationState } from "../../SpecialVisualization" - + import type Feature from "geojson" + import { GeoOperations } from "../../../Logic/GeoOperations" /** * A visualisation to pick a direction on a map background. */ export let value: UIEventSource export let state: SpecialVisualizationState = undefined - - export let mapProperties: Partial & { - readonly location: UIEventSource<{ lon: number; lat: number }> + export let args: any[] = [] + export let feature: Feature + let [lon, lat] = GeoOperations.centerpointCoordinates(feature) + let mapProperties: MapProperties = { + location: new UIEventSource({ lon, lat }), + zoom: new UIEventSource(args[0] !== undefined ? Number(args[0]) : 17), + rasterLayer: state.mapProperties.rasterLayer, + rotation: state.mapProperties.rotation, } let map: UIEventSource = new UIEventSource(undefined) let mla = new MapLibreAdaptor(map, mapProperties) mla.allowMoving.setData(false) mla.allowZooming.setData(false) - state?.mapProperties?.rasterLayer?.addCallbackAndRunD((l) => mla.rasterLayer.set(l)) - + let rotation = new UIEventSource(value.data) + rotation.addCallbackD(rotation => { + const r = (rotation + mapProperties.rotation.data + 360) % 360 + console.log("Setting value to", r) + value.setData(""+Math.floor(r)) + }, [mapProperties.rotation]) let directionElem: HTMLElement | undefined - $: value.addCallbackAndRunD((degrees) => { - if (directionElem === undefined) { + $: rotation.addCallbackAndRunD((degrees) => { + if (!directionElem?.style) { return } directionElem.style.rotate = degrees + "deg" @@ -32,13 +42,14 @@ let mainElem: HTMLElement + function onPosChange(x: number, y: number) { const rect = mainElem.getBoundingClientRect() const dx = -(rect.left + rect.right) / 2 + x const dy = (rect.top + rect.bottom) / 2 - y const angle = (180 * Math.atan2(dy, dx)) / Math.PI const angleGeo = Math.floor((450 - angle) % 360) - value.setData("" + angleGeo) + rotation.setData(angleGeo) } let isDown = false @@ -46,7 +57,7 @@
onPosChange(e.x, e.y)} on:mousedown={(e) => { isDown = true @@ -71,7 +82,7 @@
-
+
diff --git a/src/UI/InputElement/InputHelper.svelte b/src/UI/InputElement/InputHelper.svelte index 200e2afc2..ed5cbb64d 100644 --- a/src/UI/InputElement/InputHelper.svelte +++ b/src/UI/InputElement/InputHelper.svelte @@ -1,67 +1,25 @@ -{#if type === "translation"} - -{:else if type === "direction"} - -{:else if type === "date"} - -{:else if type === "time"} - -{:else if type === "points_in_time"} - -{:else if type === "color"} - -{:else if type === "image"} - -{:else if type === "tag"} - -{:else if type === "simple_tag"} - -{:else if type === "opening_hours"} - -{:else if type === "slope"} - -{:else if type === "wikidata"} - -{:else if type === "distance"} - - -{:else} - +{#if type === "distance"} + +{:else if validatorHelper !== undefined} + {/if} diff --git a/src/UI/InputElement/InputHelpers.ts b/src/UI/InputElement/InputHelpers.ts deleted file mode 100644 index bae5aaec9..000000000 --- a/src/UI/InputElement/InputHelpers.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { UIEventSource } from "../../Logic/UIEventSource" - -import { MapProperties } from "../../Models/MapProperties" -import { Feature } from "geojson" -import { GeoOperations } from "../../Logic/GeoOperations" -import { ValidatorType } from "./Validators" - -export interface InputHelperProperties { - /** - * Extra arguments which might be used by the helper component - */ - args?: (string | number | boolean)[] - - /** - * Used for map-based helpers, such as 'direction' - */ - mapProperties?: Partial & { - readonly location: UIEventSource<{ lon: number; lat: number }> - } - /** - * The feature that this question is about - * Used by the wikidata-input to read properties, which in turn is used to read the name to pre-populate the text field. - * Additionally, used for direction input to set the default location if no mapProperties with location are given - */ - feature?: Feature -} - -export default class InputHelpers { - public static hideInputField: ValidatorType[] = ["translation", "simple_tag", "tag","time"] - - /** - * Constructs a mapProperties-object for the given properties. - * Assumes that the first helper-args contains the desired zoom-level - * Used for the 'direction' input helper - * @param properties - * @private - */ - public static constructMapProperties( - properties: InputHelperProperties - ): Partial { - let location = properties?.mapProperties?.location - if (!location) { - const [lon, lat] = GeoOperations.centerpointCoordinates(properties.feature) - location = new UIEventSource<{ lon: number; lat: number }>({ lon, lat }) - } - let mapProperties: Partial = properties?.mapProperties ?? { location } - if (!mapProperties.location) { - mapProperties = { ...mapProperties, location } - } - let zoom = 17 - if (properties?.args?.[0] !== undefined) { - zoom = Number(properties.args[0]) - if (isNaN(zoom)) { - throw "Invalid zoom level for argument at 'length'-input" - } - } - if (!mapProperties.zoom) { - mapProperties = { ...mapProperties, zoom: new UIEventSource(zoom) } - } - if (!mapProperties.rasterLayer) { - /* mapProperties = { - ...mapProperties, rasterLayer: properties?.mapProperties?.rasterLayer - }*/ - } - return mapProperties - } -} diff --git a/src/UI/InputElement/Validator.ts b/src/UI/InputElement/Validator.ts index b6cc2f4d4..96703f838 100644 --- a/src/UI/InputElement/Validator.ts +++ b/src/UI/InputElement/Validator.ts @@ -1,6 +1,7 @@ import { Translation } from "../i18n/Translation" import Translations from "../i18n/Translations" import { HTMLInputTypeAttribute } from "svelte/elements" +import { ComponentType } from "svelte/types/runtime/internal/dev" /** * A 'TextFieldValidator' contains various methods to check and cleanup an entered value or to give feedback. @@ -21,6 +22,8 @@ export abstract class Validator { public readonly textArea: boolean public readonly isMeta?: boolean + public readonly inputHelper : ComponentType = undefined + public readonly hideInputField: boolean = false constructor( name: string, @@ -80,4 +83,5 @@ export abstract class Validator { public validateArguments(args: string): undefined | string { return undefined } + } diff --git a/src/UI/InputElement/Validators.ts b/src/UI/InputElement/Validators.ts index 6c830c213..50fa1e52c 100644 --- a/src/UI/InputElement/Validators.ts +++ b/src/UI/InputElement/Validators.ts @@ -1,105 +1,114 @@ import { Validator } from "./Validator" +import FloatValidator from "./Validators/FloatValidator" import StringValidator from "./Validators/StringValidator" +import PFloatValidator from "./Validators/PFloatValidator" import TextValidator from "./Validators/TextValidator" import DateValidator from "./Validators/DateValidator" +import { TimeValidator } from "./Validators/TimeValidator" import NatValidator from "./Validators/NatValidator" import IntValidator from "./Validators/IntValidator" -import DistanceValidator from "./Validators/DistanceValidator" +import PNatValidator from "./Validators/PNatValidator" import DirectionValidator from "./Validators/DirectionValidator" import WikidataValidator from "./Validators/WikidataValidator" -import PNatValidator from "./Validators/PNatValidator" -import FloatValidator from "./Validators/FloatValidator" -import PFloatValidator from "./Validators/PFloatValidator" import EmailValidator from "./Validators/EmailValidator" import UrlValidator from "./Validators/UrlValidator" -import PhoneValidator from "./Validators/PhoneValidator" import OpeningHoursValidator from "./Validators/OpeningHoursValidator" +import PhoneValidator from "./Validators/PhoneValidator" import ColorValidator from "./Validators/ColorValidator" -import SimpleTagValidator from "./Validators/SimpleTagValidator" import ImageUrlValidator from "./Validators/ImageUrlValidator" import TagKeyValidator from "./Validators/TagKeyValidator" -import TranslationValidator from "./Validators/TranslationValidator" -import FediverseValidator from "./Validators/FediverseValidator" import IconValidator from "./Validators/IconValidator" -import TagValidator from "./Validators/TagValidator" -import IdValidator from "./Validators/IdValidator" import SlopeValidator from "./Validators/SlopeValidator" +import CollectionTimesValidator from "./Validators/CollectionTimesValidator" +import IdValidator from "./Validators/IdValidator" +import FediverseValidator from "./Validators/FediverseValidator" +import SimpleTagValidator from "./Validators/SimpleTagValidator" import VeloparkValidator from "./Validators/VeloparkValidator" import NameSuggestionIndexValidator from "./Validators/NameSuggestionIndexValidator" -import CurrencyValidator from "./Validators/CurrencyValidator" import RegexValidator from "./Validators/RegexValidator" -import { TimeValidator } from "./Validators/TimeValidator" -import CollectionTimesValidator from "./Validators/CollectionTimesValidator" +import CurrencyValidator from "./Validators/CurrencyValidator" +import TagValidator from "./Validators/TagValidator" +import TranslationValidator from "./Validators/TranslationValidator" +import DistanceValidator from "./Validators/DistanceValidator" -export type ValidatorType = (typeof Validators.availableTypes)[number] +export const availableValidators = [ + "color", + "currency", + "date", + "time", + "direction", + "distance", + "email", + "fediverse", + "float", + "icon", + "id", + "image", + "int", + "key", + "nat", + "nsi", + "opening_hours", + "pfloat", + "phone", + "pnat", + "points_in_time", + "regex", + "simple_tag", + "slope", + "string", + "tag", + "text", + "translation", + "url", + "velopark", + "wikidata", +] as const +export type ValidatorType = (typeof availableValidators)[number] export default class Validators { - public static readonly availableTypes = [ - "color", - "currency", - "date", - "time", - "direction", - "distance", - "email", - "fediverse", - "float", - "icon", - "id", - "image", - "int", - "key", - "nat", - "nsi", - "opening_hours", - "pfloat", - "phone", - "pnat", - "points_in_time", - "regex", - "simple_tag", - "slope", - "string", - "tag", - "text", - "translation", - "url", - "velopark", - "wikidata", - ] as const - + public static readonly availableTypes = availableValidators public static readonly AllValidators: ReadonlyArray = [ new StringValidator(), - new TextValidator(), - new DateValidator(), - new TimeValidator(), - new NatValidator(), - new IntValidator(), - new DistanceValidator(), - new DirectionValidator(), - new WikidataValidator(), - new PNatValidator(), new FloatValidator(), new PFloatValidator(), - new EmailValidator(), - new UrlValidator(), - new PhoneValidator(), - new OpeningHoursValidator(), + new TextValidator(), + new NatValidator(), + new PNatValidator(), + new IntValidator(), + new DateValidator(), + new TimeValidator(), new ColorValidator(), - new ImageUrlValidator(), - new SimpleTagValidator(), - new TagValidator(), - new TagKeyValidator(), - new TranslationValidator(), - new IconValidator(), - new FediverseValidator(), - new IdValidator(), + + new DirectionValidator(), new SlopeValidator(), - new VeloparkValidator(), - new NameSuggestionIndexValidator(), + + + new UrlValidator(), + new EmailValidator(), + new PhoneValidator(), + new FediverseValidator(), + new ImageUrlValidator(), + + new OpeningHoursValidator(), + new CollectionTimesValidator(), new CurrencyValidator(), + + new WikidataValidator(), + + new TagKeyValidator(), + new IconValidator(), + new VeloparkValidator(), + + new IdValidator(), new RegexValidator(), - new CollectionTimesValidator() + new SimpleTagValidator(), + new TranslationValidator(), + new TagValidator(), + + new NameSuggestionIndexValidator(), + + new DistanceValidator(), ] private static _byType = Validators._byTypeConstructor() diff --git a/src/UI/InputElement/Validators/CollectionTimesValidator.ts b/src/UI/InputElement/Validators/CollectionTimesValidator.ts index 5847657c8..3fc5ce13d 100644 --- a/src/UI/InputElement/Validators/CollectionTimesValidator.ts +++ b/src/UI/InputElement/Validators/CollectionTimesValidator.ts @@ -1,7 +1,12 @@ import StringValidator from "./StringValidator" +import { ComponentType } from "svelte/types/runtime/internal/dev" +import CollectionTimes from "../Helpers/CollectionTimes/CollectionTimes.svelte" export default class CollectionTimesValidator extends StringValidator{ + public readonly inputHelper: ComponentType = CollectionTimes constructor() { super("points_in_time", "'Points in time' are points according to a fixed schedule, e.g. 'every monday at 10:00'. They are typically used for postbox collection times or times of mass at a place of worship") } + + } diff --git a/src/UI/InputElement/Validators/ColorValidator.ts b/src/UI/InputElement/Validators/ColorValidator.ts index 88dd5a893..df984e53e 100644 --- a/src/UI/InputElement/Validators/ColorValidator.ts +++ b/src/UI/InputElement/Validators/ColorValidator.ts @@ -1,7 +1,11 @@ import { Validator } from "../Validator" +import ColorInput from "../Helpers/ColorInput.svelte" export default class ColorValidator extends Validator { + inputHelper = ColorInput + constructor() { super("color", "Shows a color picker") } + } diff --git a/src/UI/InputElement/Validators/DateValidator.ts b/src/UI/InputElement/Validators/DateValidator.ts index 340b8159a..5cc02c7c7 100644 --- a/src/UI/InputElement/Validators/DateValidator.ts +++ b/src/UI/InputElement/Validators/DateValidator.ts @@ -1,6 +1,10 @@ import { Validator } from "../Validator" +import DateInput from "../Helpers/DateInput.svelte" export default class DateValidator extends Validator { + public readonly inputHelper = DateInput + public readonly hideInputField = true + constructor() { super("date", "A date with date picker") } @@ -25,4 +29,5 @@ export default class DateValidator extends Validator { return [year, month, day].join("-") } + } diff --git a/src/UI/InputElement/Validators/DirectionValidator.ts b/src/UI/InputElement/Validators/DirectionValidator.ts index 4cf14d9ad..24719d491 100644 --- a/src/UI/InputElement/Validators/DirectionValidator.ts +++ b/src/UI/InputElement/Validators/DirectionValidator.ts @@ -1,6 +1,9 @@ import IntValidator from "./IntValidator" +import DirectionInput from "../Helpers/DirectionInput.svelte" export default class DirectionValidator extends IntValidator { + + public readonly inputHelper = DirectionInput constructor() { super( "direction", diff --git a/src/UI/InputElement/Validators/DistanceValidator.ts b/src/UI/InputElement/Validators/DistanceValidator.ts index b51130159..ddca39f09 100644 --- a/src/UI/InputElement/Validators/DistanceValidator.ts +++ b/src/UI/InputElement/Validators/DistanceValidator.ts @@ -3,6 +3,7 @@ import { Utils } from "../../../Utils" import { eliCategory } from "../../../Models/RasterLayerProperties" export default class DistanceValidator extends Validator { + private readonly docs: string = [ "#### Helper-arguments", "Options are:", @@ -58,4 +59,5 @@ export default class DistanceValidator extends Validator { } return undefined } + } diff --git a/src/UI/InputElement/Validators/ImageUrlValidator.ts b/src/UI/InputElement/Validators/ImageUrlValidator.ts index 5d9e11507..2d27f1d74 100644 --- a/src/UI/InputElement/Validators/ImageUrlValidator.ts +++ b/src/UI/InputElement/Validators/ImageUrlValidator.ts @@ -1,9 +1,11 @@ import UrlValidator from "./UrlValidator" import { Translation } from "../../i18n/Translation" +import ImageHelper from "../Helpers/ImageHelper.svelte" export default class ImageUrlValidator extends UrlValidator { private static readonly allowedExtensions = ["jpg", "jpeg", "svg", "png"] public readonly isMeta = true + public readonly inputHelper = ImageHelper constructor() { super( @@ -37,4 +39,6 @@ export default class ImageUrlValidator extends UrlValidator { } return ImageUrlValidator.hasValidExternsion(str) } + } + diff --git a/src/UI/InputElement/Validators/OpeningHoursValidator.ts b/src/UI/InputElement/Validators/OpeningHoursValidator.ts index 66990aaeb..a296f6cf0 100644 --- a/src/UI/InputElement/Validators/OpeningHoursValidator.ts +++ b/src/UI/InputElement/Validators/OpeningHoursValidator.ts @@ -1,7 +1,12 @@ import { Validator } from "../Validator" import MarkdownUtils from "../../../Utils/MarkdownUtils" +import { ComponentType } from "svelte/types/runtime/internal/dev" +import OpeningHoursInput from "../Helpers/OpeningHoursInput.svelte" export default class OpeningHoursValidator extends Validator { + + public readonly inputHelper= OpeningHoursInput + constructor() { super( "opening_hours", @@ -39,4 +44,6 @@ export default class OpeningHoursValidator extends Validator { ].join("\n") ) } + + } diff --git a/src/UI/InputElement/Validators/SimpleTagValidator.ts b/src/UI/InputElement/Validators/SimpleTagValidator.ts index f39ca9d1d..732874f11 100644 --- a/src/UI/InputElement/Validators/SimpleTagValidator.ts +++ b/src/UI/InputElement/Validators/SimpleTagValidator.ts @@ -2,12 +2,15 @@ import { Validator } from "../Validator" import { Translation } from "../../i18n/Translation" import Translations from "../../i18n/Translations" import TagKeyValidator from "./TagKeyValidator" +import SimpleTagInput from "../Helpers/SimpleTagInput.svelte" /** * Checks that the input conforms `key=value`, where `key` and `value` don't have too much weird characters */ export default class SimpleTagValidator extends Validator { private static readonly KeyValidator = new TagKeyValidator() + public readonly inputHelper = SimpleTagInput + public readonly hideInputField = true public readonly isMeta = true constructor() { @@ -50,4 +53,6 @@ export default class SimpleTagValidator extends Validator { isValid(tag: string, _): boolean { return this.getFeedback(tag, _) === undefined } + } + diff --git a/src/UI/InputElement/Validators/SlopeValidator.ts b/src/UI/InputElement/Validators/SlopeValidator.ts index 8409fa49b..79e033090 100644 --- a/src/UI/InputElement/Validators/SlopeValidator.ts +++ b/src/UI/InputElement/Validators/SlopeValidator.ts @@ -1,6 +1,9 @@ import FloatValidator from "./FloatValidator" +import SlopeInput from "../Helpers/SlopeInput.svelte" export default class SlopeValidator extends FloatValidator { + public readonly inputHelper =SlopeInput + constructor() { super( "slope", @@ -40,4 +43,5 @@ export default class SlopeValidator extends FloatValidator { } return super.reformat(str) + lastChar } + } diff --git a/src/UI/InputElement/Validators/TagValidator.ts b/src/UI/InputElement/Validators/TagValidator.ts index 8c69dd71d..7900f348f 100644 --- a/src/UI/InputElement/Validators/TagValidator.ts +++ b/src/UI/InputElement/Validators/TagValidator.ts @@ -1,11 +1,14 @@ import { Validator } from "../Validator" import { Translation } from "../../i18n/Translation" +import TagInput from "../Helpers/TagInput.svelte" /** * Checks that the input conforms a JSON-encoded tag expression or a simpleTag`key=value`, */ export default class TagValidator extends Validator { public readonly isMeta = true + public readonly inputHelper = TagInput + public readonly hideInputField = true constructor() { super("tag", "A simple tag of the format `key=value` OR a tagExpression") @@ -18,4 +21,5 @@ export default class TagValidator extends Validator { isValid(tag: string, _): boolean { return this.getFeedback(tag, _) === undefined } + } diff --git a/src/UI/InputElement/Validators/TimeValidator.ts b/src/UI/InputElement/Validators/TimeValidator.ts index 867d35226..2d32ada1d 100644 --- a/src/UI/InputElement/Validators/TimeValidator.ts +++ b/src/UI/InputElement/Validators/TimeValidator.ts @@ -1,11 +1,15 @@ import { Validator } from "../Validator" +import TimeInput from "../Helpers/TimeInput.svelte" export class TimeValidator extends Validator { - inputmode = "time" + public readonly inputmode = "time" + public readonly inputHelper = TimeInput + public readonly hideInputField = true constructor() { super("time", "A time picker") } + } diff --git a/src/UI/InputElement/Validators/TranslationValidator.ts b/src/UI/InputElement/Validators/TranslationValidator.ts index f0a602e72..ca51e38c2 100644 --- a/src/UI/InputElement/Validators/TranslationValidator.ts +++ b/src/UI/InputElement/Validators/TranslationValidator.ts @@ -1,7 +1,10 @@ import { Validator } from "../Validator" +import TranslationInput from "../Helpers/TranslationInput.svelte" export default class TranslationValidator extends Validator { + public readonly inputHelper = TranslationInput public readonly isMeta = true + public readonly hideInputField = true constructor() { super("translation", "Makes sure the the string is of format `Record` ") } @@ -14,4 +17,5 @@ export default class TranslationValidator extends Validator { return false } } + } diff --git a/src/UI/InputElement/Validators/WikidataValidator.ts b/src/UI/InputElement/Validators/WikidataValidator.ts index acbaf9ffc..58c9c46b5 100644 --- a/src/UI/InputElement/Validators/WikidataValidator.ts +++ b/src/UI/InputElement/Validators/WikidataValidator.ts @@ -3,9 +3,11 @@ import { Validator } from "../Validator" import { Translation } from "../../i18n/Translation" import Translations from "../../i18n/Translations" import MarkdownUtils from "../../../Utils/MarkdownUtils" +import WikidataInputHelper from "../Helpers/WikidataInputHelper.svelte" export default class WikidataValidator extends Validator { public static readonly _searchCache = new Map>() + public readonly inputHelper = WikidataInputHelper private static docs = [ "#### Helper arguments", @@ -182,4 +184,5 @@ Another example is to search for species and trees: } return clipped } + } diff --git a/src/UI/Popup/TagRendering/FreeformInput.svelte b/src/UI/Popup/TagRendering/FreeformInput.svelte index 23e7bacac..9ca78679e 100644 --- a/src/UI/Popup/TagRendering/FreeformInput.svelte +++ b/src/UI/Popup/TagRendering/FreeformInput.svelte @@ -8,8 +8,8 @@ import InputHelper from "../../InputElement/InputHelper.svelte" import type { Feature } from "geojson" import { Unit } from "../../../Models/Unit" - import InputHelpers from "../../InputElement/InputHelpers" import type { SpecialVisualizationState } from "../../SpecialVisualization" + import Validators from "../../InputElement/Validators" export let value: UIEventSource export let unvalidatedText: UIEventSource = new UIEventSource(value.data) @@ -29,6 +29,7 @@ } const dispatch = createEventDispatcher<{ selected }>() + let hideInput = Validators.get(config.freeform.type).hideInputField export let feedback: UIEventSource onDestroy( value.addCallbackD(() => { @@ -44,6 +45,16 @@
{#if inline} + {#if hideInput} + + {:else} + {/if} - {:else if InputHelpers.hideInputField.indexOf(config.freeform.type) < 0} + {:else if !hideInput} {/if} + {#if !(inline && hideInput)} + {/if}
diff --git a/src/assets/svg/Direction_stroke.svelte b/src/assets/svg/Direction_stroke.svelte index c6e5a51af..d1d0d8711 100644 --- a/src/assets/svg/Direction_stroke.svelte +++ b/src/assets/svg/Direction_stroke.svelte @@ -1,4 +1,4 @@ - \ No newline at end of file + \ No newline at end of file