forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			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,
 | 
						|
        })
 | 
						|
    }
 | 
						|
}
 |