151 lines
5.9 KiB
TypeScript
151 lines
5.9 KiB
TypeScript
import { ValidatorType } from "./Validators"
|
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
|
import SvelteUIElement from "../Base/SvelteUIElement"
|
|
import DirectionInput from "./Helpers/DirectionInput.svelte"
|
|
import { MapProperties } from "../../Models/MapProperties"
|
|
import DateInput from "./Helpers/DateInput.svelte"
|
|
import ColorInput from "./Helpers/ColorInput.svelte"
|
|
import BaseUIElement from "../BaseUIElement"
|
|
import OpeningHoursInput from "../OpeningHours/OpeningHoursInput"
|
|
import WikidataSearchBox from "../Wikipedia/WikidataSearchBox"
|
|
import Wikidata from "../../Logic/Web/Wikidata"
|
|
import { Utils } from "../../Utils"
|
|
import Locale from "../i18n/Locale"
|
|
import { Feature } from "geojson"
|
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
|
|
|
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<MapProperties> & {
|
|
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 readonly AvailableInputHelpers: Readonly<
|
|
Partial<
|
|
Record<
|
|
ValidatorType,
|
|
(
|
|
value: UIEventSource<string>,
|
|
extraProperties?: InputHelperProperties
|
|
) => BaseUIElement
|
|
>
|
|
>
|
|
> = {
|
|
direction: (value, properties) =>
|
|
new SvelteUIElement(DirectionInput, {
|
|
value,
|
|
mapProperties: InputHelpers.constructMapProperties(properties),
|
|
}),
|
|
date: (value) => new SvelteUIElement(DateInput, { value }),
|
|
color: (value) => new SvelteUIElement(ColorInput, { value }),
|
|
opening_hours: (value) => new OpeningHoursInput(value),
|
|
wikidata: InputHelpers.constructWikidataHelper,
|
|
} as const
|
|
|
|
/**
|
|
* Constructs a mapProperties-object for the given properties.
|
|
* Assumes that the first helper-args contains the desired zoom-level
|
|
* @param properties
|
|
* @private
|
|
*/
|
|
private static constructMapProperties(
|
|
properties: InputHelperProperties
|
|
): Partial<MapProperties> {
|
|
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<MapProperties> = 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<number>(zoom) }
|
|
}
|
|
return mapProperties
|
|
}
|
|
private static constructWikidataHelper(
|
|
value: UIEventSource<string>,
|
|
props: InputHelperProperties
|
|
) {
|
|
const inputHelperOptions = props
|
|
const args = inputHelperOptions.args ?? []
|
|
const searchKey = <string>args[0] ?? "name"
|
|
|
|
const searchFor = <string>(
|
|
(inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "")
|
|
)
|
|
|
|
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
|
|
const options: any = args[1]
|
|
if (searchFor !== undefined && options !== undefined) {
|
|
const prefixes = <string[] | Record<string, string[]>>options["removePrefixes"] ?? []
|
|
const postfixes = <string[] | Record<string, string[]>>options["removePostfixes"] ?? []
|
|
const defaultValueCandidate = Locale.language.map((lg) => {
|
|
const prefixesUnrwapped: RegExp[] = (
|
|
Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? []
|
|
).map((s) => new RegExp("^" + s, "i"))
|
|
const postfixesUnwrapped: RegExp[] = (
|
|
Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? []
|
|
).map((s) => new RegExp(s + "$", "i"))
|
|
let clipped = searchFor
|
|
|
|
for (const postfix of postfixesUnwrapped) {
|
|
const match = searchFor.match(postfix)
|
|
if (match !== null) {
|
|
clipped = searchFor.substring(0, searchFor.length - match[0].length)
|
|
break
|
|
}
|
|
}
|
|
|
|
for (const prefix of prefixesUnrwapped) {
|
|
const match = searchFor.match(prefix)
|
|
if (match !== null) {
|
|
clipped = searchFor.substring(match[0].length)
|
|
break
|
|
}
|
|
}
|
|
return clipped
|
|
})
|
|
|
|
defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped))
|
|
}
|
|
|
|
let instanceOf: number[] = Utils.NoNull(
|
|
(options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
|
)
|
|
let notInstanceOf: number[] = Utils.NoNull(
|
|
(options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
|
)
|
|
|
|
return new WikidataSearchBox({
|
|
value,
|
|
searchText: searchForValue,
|
|
instanceOf,
|
|
notInstanceOf,
|
|
})
|
|
}
|
|
}
|