forked from MapComplete/MapComplete
		
	Refactoring: cleanup, scroll questions into view, add placeholders
This commit is contained in:
		
							parent
							
								
									55e12c32e5
								
							
						
					
					
						commit
						7e3d0e6a79
					
				
					 16 changed files with 217 additions and 1181 deletions
				
			
		| 
						 | 
					@ -8,8 +8,8 @@
 | 
				
			||||||
  const dispatch = createEventDispatcher<{ close }>();
 | 
					  const dispatch = createEventDispatcher<{ close }>();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="absolute top-0 right-0 h-screen overflow-auto w-full md:w-6/12 lg:w-5/12 xl:w-4/12">
 | 
					<div class="absolute top-0 right-0 h-screen overflow-auto w-full md:w-6/12 lg:w-5/12 xl:w-4/12 drop-shadow-2xl">
 | 
				
			||||||
  <div class="flex flex-col m-0 p-4 sm:p-6 normal-background normal-background">
 | 
					  <div class="flex flex-col m-0 normal-background">
 | 
				
			||||||
    <slot name="close-button">
 | 
					    <slot name="close-button">
 | 
				
			||||||
      <div class="w-8 h-8 absolute right-10 top-10 cursor-pointer" on:click={() => dispatch("close")}>
 | 
					      <div class="w-8 h-8 absolute right-10 top-10 cursor-pointer" on:click={() => dispatch("close")}>
 | 
				
			||||||
        <XCircleIcon />
 | 
					        <XCircleIcon />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,33 +49,10 @@ export default abstract class BaseUIElement {
 | 
				
			||||||
        return this
 | 
					        return this
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public ScrollToTop() {
 | 
					    public ScrollIntoView() {
 | 
				
			||||||
        this._constructedHtmlElement?.scrollTo(0, 0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public ScrollIntoView(options?: { onlyIfPartiallyHidden?: boolean }) {
 | 
					 | 
				
			||||||
        if (this._constructedHtmlElement === undefined) {
 | 
					        if (this._constructedHtmlElement === undefined) {
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        let alignToTop = true
 | 
					 | 
				
			||||||
        if (options?.onlyIfPartiallyHidden) {
 | 
					 | 
				
			||||||
            // Is the element completely in the view?
 | 
					 | 
				
			||||||
            const parentRect = Utils.findParentWithScrolling(
 | 
					 | 
				
			||||||
                this._constructedHtmlElement.parentElement
 | 
					 | 
				
			||||||
            ).getBoundingClientRect()
 | 
					 | 
				
			||||||
            const elementRect = this._constructedHtmlElement.getBoundingClientRect()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Check if the element is within the vertical bounds of the parent element
 | 
					 | 
				
			||||||
            const topIsVisible = elementRect.top >= parentRect.top
 | 
					 | 
				
			||||||
            const bottomIsVisible = elementRect.bottom <= parentRect.bottom
 | 
					 | 
				
			||||||
            const inView = topIsVisible && bottomIsVisible
 | 
					 | 
				
			||||||
            if (inView) {
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (topIsVisible) {
 | 
					 | 
				
			||||||
                alignToTop = false
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this._constructedHtmlElement?.scrollIntoView({
 | 
					        this._constructedHtmlElement?.scrollIntoView({
 | 
				
			||||||
            behavior: "smooth",
 | 
					            behavior: "smooth",
 | 
				
			||||||
            block: "start",
 | 
					            block: "start",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,14 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import type { Feature } from "geojson";
 | 
					    import type {Feature} from "geojson";
 | 
				
			||||||
  import { UIEventSource } from "../../Logic/UIEventSource";
 | 
					    import {UIEventSource} from "../../Logic/UIEventSource";
 | 
				
			||||||
    import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
 | 
					    import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
 | 
				
			||||||
  import type { SpecialVisualizationState } from "../SpecialVisualization";
 | 
					    import type {SpecialVisualizationState} from "../SpecialVisualization";
 | 
				
			||||||
    import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
 | 
					    import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte";
 | 
				
			||||||
    import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
 | 
					    import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
 | 
				
			||||||
  import { onDestroy } from "svelte";
 | 
					    import {onDestroy} from "svelte";
 | 
				
			||||||
    import Translations from "../i18n/Translations";
 | 
					    import Translations from "../i18n/Translations";
 | 
				
			||||||
    import Tr from "../Base/Tr.svelte";
 | 
					    import Tr from "../Base/Tr.svelte";
 | 
				
			||||||
 | 
					    import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let state: SpecialVisualizationState;
 | 
					    export let state: SpecialVisualizationState;
 | 
				
			||||||
    export let layer: LayerConfig;
 | 
					    export let layer: LayerConfig;
 | 
				
			||||||
| 
						 | 
					@ -29,18 +30,21 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if _tags._deleted === "yes"}
 | 
					{#if _tags._deleted === "yes"}
 | 
				
			||||||
  <Tr t={ Translations.t.delete.isDeleted} />
 | 
					    <Tr t={ Translations.t.delete.isDeleted}/>
 | 
				
			||||||
{:else}
 | 
					{:else}
 | 
				
			||||||
  <div>
 | 
					    <div class="absolute flex flex-col h-full normal-background">
 | 
				
			||||||
    <div class="flex flex-col sm:flex-row flex-grow justify-between">
 | 
					        <div class="flex border-b-2 border-black shadow justify-between items-center">
 | 
				
			||||||
 | 
					            <div class="flex flex-col">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <!-- Title element-->
 | 
					                <!-- Title element-->
 | 
				
			||||||
                <h3>
 | 
					                <h3>
 | 
				
			||||||
        <TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags} {layer}></TagRenderingAnswer>
 | 
					                    <TagRenderingAnswer config={layer.title} {selectedElement} {state} {tags}
 | 
				
			||||||
 | 
					                                        {layer}></TagRenderingAnswer>
 | 
				
			||||||
                </h3>
 | 
					                </h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2">
 | 
					                <div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2">
 | 
				
			||||||
                    {#each layer.titleIcons as titleIconConfig}
 | 
					                    {#each layer.titleIcons as titleIconConfig}
 | 
				
			||||||
          {#if ( titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties(_tags) ?? true) && titleIconConfig.IsKnown(_tags)}
 | 
					                        {#if (titleIconConfig.condition?.matchesProperties(_tags) ?? true) && (titleIconConfig.metacondition?.matchesProperties(_tags) ?? true) && titleIconConfig.IsKnown(_tags)}
 | 
				
			||||||
                            <div class="w-8 h-8">
 | 
					                            <div class="w-8 h-8">
 | 
				
			||||||
                                <TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state}
 | 
					                                <TagRenderingAnswer config={titleIconConfig} {tags} {selectedElement} {state}
 | 
				
			||||||
                                                    {layer}></TagRenderingAnswer>
 | 
					                                                    {layer}></TagRenderingAnswer>
 | 
				
			||||||
| 
						 | 
					@ -49,12 +53,12 @@
 | 
				
			||||||
                    {/each}
 | 
					                    {/each}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <XCircleIcon class="w-8 h-8 cursor-pointer" on:click={() => state.selectedElement.setData(undefined)}/>
 | 
				
			||||||
    <div class="flex flex-col">
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="flex flex-col overflow-y-auto">
 | 
				
			||||||
            {#each layer.tagRenderings as config (config.id)}
 | 
					            {#each layer.tagRenderings as config (config.id)}
 | 
				
			||||||
        {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({ ..._tags, ..._metatags }))}
 | 
					                {#if (config.condition === undefined || config.condition.matchesProperties(_tags)) && (config.metacondition === undefined || config.metacondition.matchesProperties({..._tags, ..._metatags}))}
 | 
				
			||||||
                    {#if config.IsKnown(_tags)}
 | 
					                    {#if config.IsKnown(_tags)}
 | 
				
			||||||
                        <TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}
 | 
					                        <TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}
 | 
				
			||||||
                                              {highlightedRendering}></TagRenderingEditable>
 | 
					                                              {highlightedRendering}></TagRenderingEditable>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,13 +14,16 @@
 | 
				
			||||||
  export let type: ValidatorType;
 | 
					  export let type: ValidatorType;
 | 
				
			||||||
  export let feedback: UIEventSource<Translation> | undefined = undefined;
 | 
					  export let feedback: UIEventSource<Translation> | undefined = undefined;
 | 
				
			||||||
  export let getCountry: () => string | undefined
 | 
					  export let getCountry: () => string | undefined
 | 
				
			||||||
  
 | 
					  export let placeholder: string | Translation | undefined
 | 
				
			||||||
  let validator : Validator = Validators.get(type)
 | 
					  let validator : Validator = Validators.get(type)
 | 
				
			||||||
 | 
					  let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    // The type changed -> reset some values
 | 
					    // The type changed -> reset some values
 | 
				
			||||||
    validator = Validators.get(type)
 | 
					    validator = Validators.get(type)
 | 
				
			||||||
    _value.setData(value.data ?? "")
 | 
					    _value.setData(value.data ?? "")
 | 
				
			||||||
    feedback =  feedback?.setData(validator?.getFeedback(_value.data, getCountry));
 | 
					    feedback =  feedback?.setData(validator?.getFeedback(_value.data, getCountry));
 | 
				
			||||||
 | 
					    _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  onDestroy(_value.addCallbackAndRun(v => {
 | 
					  onDestroy(_value.addCallbackAndRun(v => {
 | 
				
			||||||
| 
						 | 
					@ -50,10 +53,10 @@
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if validator.textArea}
 | 
					{#if validator.textArea}
 | 
				
			||||||
  <textarea class="w-full" bind:value={$_value} inputmode={validator.inputmode ?? "text"}></textarea>
 | 
					  <textarea class="w-full" bind:value={$_value} inputmode={validator.inputmode ?? "text"} placeholder={_placeholder}></textarea>
 | 
				
			||||||
{:else }
 | 
					{:else }
 | 
				
			||||||
  <span class="inline-flex">
 | 
					  <span class="inline-flex">
 | 
				
			||||||
    <input bind:this={htmlElem} bind:value={$_value} inputmode={validator.inputmode ?? "text"}>
 | 
					    <input bind:this={htmlElem} bind:value={$_value} inputmode={validator.inputmode ?? "text"} placeholder={_placeholder}>
 | 
				
			||||||
    {#if !$isValid}
 | 
					    {#if !$isValid}
 | 
				
			||||||
      <ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon>
 | 
					      <ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,10 @@ export abstract class Validator {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public getPlaceholder(){
 | 
				
			||||||
 | 
					        return Translations.t.validation[this.name].description
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public isValid(string: string, requestCountry?: () => string): boolean {
 | 
					    public isValid(string: string, requestCountry?: () => string): boolean {
 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,9 +63,3 @@
 | 
				
			||||||
<section>
 | 
					<section>
 | 
				
			||||||
  <ToSvelte construct={tagsTable} />
 | 
					  <ToSvelte construct={tagsTable} />
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="scss">
 | 
					 | 
				
			||||||
  section {
 | 
					 | 
				
			||||||
    @apply border border-solid border-black rounded-2xl p-4 block;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,121 +0,0 @@
 | 
				
			||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
					 | 
				
			||||||
import TagRenderingQuestion from "./TagRenderingQuestion"
 | 
					 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					 | 
				
			||||||
import TagRenderingAnswer from "./TagRenderingAnswer"
 | 
					 | 
				
			||||||
import Toggle from "../Input/Toggle"
 | 
					 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					 | 
				
			||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
					 | 
				
			||||||
import { Unit } from "../../Models/Unit"
 | 
					 | 
				
			||||||
import Lazy from "../Base/Lazy"
 | 
					 | 
				
			||||||
import { FixedUiElement } from "../Base/FixedUiElement"
 | 
					 | 
				
			||||||
import { EditButton } from "./SaveButton"
 | 
					 | 
				
			||||||
import { UploadableTag } from "../../Logic/Tags/TagUtils"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class EditableTagRendering extends Toggle {
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        tags: UIEventSource<any>,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        units: Unit[],
 | 
					 | 
				
			||||||
        state,
 | 
					 | 
				
			||||||
        options: {
 | 
					 | 
				
			||||||
            editMode?: UIEventSource<boolean>
 | 
					 | 
				
			||||||
            innerElementClasses?: string
 | 
					 | 
				
			||||||
            /* Classes applied _only_ on the rendered element, not on the question*/
 | 
					 | 
				
			||||||
            answerElementClasses?: string
 | 
					 | 
				
			||||||
            /* Default will apply the tags to the relevant object, only use in special cases */
 | 
					 | 
				
			||||||
            createSaveButton?: (src: Store<UploadableTag>) => BaseUIElement
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        // The tagrendering is hidden if:
 | 
					 | 
				
			||||||
        // - The answer is unknown. The questionbox will then show the question
 | 
					 | 
				
			||||||
        // - There is a condition hiding the answer
 | 
					 | 
				
			||||||
        const renderingIsShown = tags.map(
 | 
					 | 
				
			||||||
            (tags) =>
 | 
					 | 
				
			||||||
                configuration.IsKnown(tags) &&
 | 
					 | 
				
			||||||
                (configuration?.condition?.matchesProperties(tags) ?? true)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        const editMode = options.editMode ?? new UIEventSource<boolean>(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        super(
 | 
					 | 
				
			||||||
            new Lazy(() => {
 | 
					 | 
				
			||||||
                let rendering = EditableTagRendering.CreateRendering(
 | 
					 | 
				
			||||||
                    state,
 | 
					 | 
				
			||||||
                    tags,
 | 
					 | 
				
			||||||
                    configuration,
 | 
					 | 
				
			||||||
                    units,
 | 
					 | 
				
			||||||
                    editMode,
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        saveButtonConstructor: options?.createSaveButton,
 | 
					 | 
				
			||||||
                        answerElementClasses: options?.answerElementClasses,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                rendering.SetClass(options.innerElementClasses)
 | 
					 | 
				
			||||||
                if (state?.featureSwitchIsDebugging?.data || state?.featureSwitchIsTesting?.data) {
 | 
					 | 
				
			||||||
                    rendering = new Combine([
 | 
					 | 
				
			||||||
                        new FixedUiElement(configuration.id).SetClass("self-end subtle"),
 | 
					 | 
				
			||||||
                        rendering,
 | 
					 | 
				
			||||||
                    ]).SetClass("flex flex-col")
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return rendering
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
            undefined,
 | 
					 | 
				
			||||||
            renderingIsShown
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        const self = this
 | 
					 | 
				
			||||||
        editMode.addCallback((editing) => {
 | 
					 | 
				
			||||||
            if (editing) {
 | 
					 | 
				
			||||||
                self.ScrollIntoView()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static CreateRendering(
 | 
					 | 
				
			||||||
        state: any /*FeaturePipelineState*/,
 | 
					 | 
				
			||||||
        tags: UIEventSource<any>,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        units: Unit[],
 | 
					 | 
				
			||||||
        editMode: UIEventSource<boolean>,
 | 
					 | 
				
			||||||
        options?: {
 | 
					 | 
				
			||||||
            saveButtonConstructor?: (src: Store<UploadableTag>) => BaseUIElement
 | 
					 | 
				
			||||||
            answerElementClasses?: string
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ): BaseUIElement {
 | 
					 | 
				
			||||||
        const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration, state)
 | 
					 | 
				
			||||||
        answer.SetClass("w-full")
 | 
					 | 
				
			||||||
        let rendering = answer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (configuration.question !== undefined && (state?.featureSwitchUserbadge?.data ?? true)) {
 | 
					 | 
				
			||||||
            // We have a question and editing is enabled
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const question = new Lazy(
 | 
					 | 
				
			||||||
                () =>
 | 
					 | 
				
			||||||
                    new TagRenderingQuestion(tags, configuration, state, {
 | 
					 | 
				
			||||||
                        units: units,
 | 
					 | 
				
			||||||
                        cancelButton: Translations.t.general.cancel
 | 
					 | 
				
			||||||
                            .Clone()
 | 
					 | 
				
			||||||
                            .SetClass("btn btn-secondary")
 | 
					 | 
				
			||||||
                            .onClick(() => {
 | 
					 | 
				
			||||||
                                editMode.setData(false)
 | 
					 | 
				
			||||||
                            }),
 | 
					 | 
				
			||||||
                        saveButtonConstr: options?.saveButtonConstructor,
 | 
					 | 
				
			||||||
                        afterSave: () => {
 | 
					 | 
				
			||||||
                            editMode.setData(false)
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const answerWithEditButton = new Combine([
 | 
					 | 
				
			||||||
                answer,
 | 
					 | 
				
			||||||
                new EditButton(state?.osmConnection, () => {
 | 
					 | 
				
			||||||
                    editMode.setData(true)
 | 
					 | 
				
			||||||
                    question.ScrollIntoView({
 | 
					 | 
				
			||||||
                        onlyIfPartiallyHidden: true,
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                }),
 | 
					 | 
				
			||||||
            ]).SetClass("flex justify-between w-full " + (options?.answerElementClasses ?? ""))
 | 
					 | 
				
			||||||
            rendering = new Toggle(question, answerWithEditButton, editMode)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return rendering
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,139 +0,0 @@
 | 
				
			||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
					 | 
				
			||||||
import TagRenderingQuestion from "./TagRenderingQuestion"
 | 
					 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					 | 
				
			||||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
					 | 
				
			||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
					 | 
				
			||||||
import { Unit } from "../../Models/Unit"
 | 
					 | 
				
			||||||
import Lazy from "../Base/Lazy"
 | 
					 | 
				
			||||||
import { OsmServiceState } from "../../Logic/Osm/OsmConnection"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @deprecated
 | 
					 | 
				
			||||||
 * This element is getting stripped and is not used anymore
 | 
					 | 
				
			||||||
 * Generates all the questions, one by one
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export default class QuestionBox extends VariableUiElement {
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        state,
 | 
					 | 
				
			||||||
        options: {
 | 
					 | 
				
			||||||
            tagsSource: UIEventSource<any>
 | 
					 | 
				
			||||||
            tagRenderings: TagRenderingConfig[]
 | 
					 | 
				
			||||||
            units: Unit[]
 | 
					 | 
				
			||||||
            showAllQuestionsAtOnce?: boolean | Store<boolean>
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        const skippedQuestions: UIEventSource<number[]> = new UIEventSource<number[]>([])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const tagsSource = options.tagsSource
 | 
					 | 
				
			||||||
        const units = options.units
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let focus: () => void = () => {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const tagRenderingQuestions = tagRenderings.map(
 | 
					 | 
				
			||||||
            (tagRendering, i) =>
 | 
					 | 
				
			||||||
                new Lazy(
 | 
					 | 
				
			||||||
                    () =>
 | 
					 | 
				
			||||||
                        new TagRenderingQuestion(tagsSource, tagRendering, state, {
 | 
					 | 
				
			||||||
                            units: units,
 | 
					 | 
				
			||||||
                            afterSave: () => {
 | 
					 | 
				
			||||||
                                // We save and indicate progress by pinging and recalculating
 | 
					 | 
				
			||||||
                                skippedQuestions.ping()
 | 
					 | 
				
			||||||
                                focus()
 | 
					 | 
				
			||||||
                            },
 | 
					 | 
				
			||||||
                            cancelButton: Translations.t.general.skip
 | 
					 | 
				
			||||||
                                .Clone()
 | 
					 | 
				
			||||||
                                .SetClass("btn btn-secondary")
 | 
					 | 
				
			||||||
                                .onClick(() => {
 | 
					 | 
				
			||||||
                                    skippedQuestions.data.push(i)
 | 
					 | 
				
			||||||
                                    skippedQuestions.ping()
 | 
					 | 
				
			||||||
                                    focus()
 | 
					 | 
				
			||||||
                                }),
 | 
					 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        tagsSource.map(
 | 
					 | 
				
			||||||
            (tags) => {
 | 
					 | 
				
			||||||
                if (tags === undefined) {
 | 
					 | 
				
			||||||
                    return undefined
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                for (let i = 0; i < tagRenderingQuestions.length; i++) {
 | 
					 | 
				
			||||||
                    let tagRendering = tagRenderings[i]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (skippedQuestions.data.indexOf(i) >= 0) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (tagRendering.IsKnown(tags)) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (tagRendering.condition) {
 | 
					 | 
				
			||||||
                        if (!tagRendering.condition.matchesProperties(tags)) {
 | 
					 | 
				
			||||||
                            // Filtered away by the condition, so it is kindof known
 | 
					 | 
				
			||||||
                            continue
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // this value is NOT known - this is the question we have to show!
 | 
					 | 
				
			||||||
                    return i
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return undefined // The questions are depleted
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            [skippedQuestions]
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const questionsToAsk: Store<BaseUIElement[]> = tagsSource.map(
 | 
					 | 
				
			||||||
            (tags) => {
 | 
					 | 
				
			||||||
                if (tags === undefined) {
 | 
					 | 
				
			||||||
                    return []
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const qs = []
 | 
					 | 
				
			||||||
                for (let i = 0; i < tagRenderingQuestions.length; i++) {
 | 
					 | 
				
			||||||
                    let tagRendering = tagRenderings[i]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (skippedQuestions.data.indexOf(i) >= 0) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (tagRendering.IsKnown(tags)) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (tagRendering.condition && !tagRendering.condition.matchesProperties(tags)) {
 | 
					 | 
				
			||||||
                        // Filtered away by the condition, so it is kindof known
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // this value is NOT known - this is the question we have to show!
 | 
					 | 
				
			||||||
                    qs.push(tagRenderingQuestions[i])
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return qs
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            [skippedQuestions]
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        super(
 | 
					 | 
				
			||||||
            questionsToAsk.map(
 | 
					 | 
				
			||||||
                (allQuestions) => {
 | 
					 | 
				
			||||||
                    const apiState: OsmServiceState = state.osmConnection.apiIsOnline.data
 | 
					 | 
				
			||||||
                    if (apiState !== "online" && apiState !== "unknown") {
 | 
					 | 
				
			||||||
                        return undefined
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const els: BaseUIElement[] = []
 | 
					 | 
				
			||||||
                    if (
 | 
					 | 
				
			||||||
                        options.showAllQuestionsAtOnce === true ||
 | 
					 | 
				
			||||||
                        options.showAllQuestionsAtOnce["data"]
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                        els.push(...questionsToAsk.data)
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        els.push(allQuestions[0])
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return new Combine(els).SetClass("block mb-8")
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                [state.osmConnection.apiIsOnline]
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        focus = () => this.ScrollIntoView()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,11 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let feature: Feature = undefined;
 | 
					  export let feature: Feature = undefined;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  let placeholder = config.freeform?.placeholder
 | 
				
			||||||
 | 
					  $: {
 | 
				
			||||||
 | 
					    placeholder = config.freeform?.placeholder
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
 | 
					  let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let dispatch = createEventDispatcher<{ "selected" }>();
 | 
					  let dispatch = createEventDispatcher<{ "selected" }>();
 | 
				
			||||||
| 
						 | 
					@ -29,11 +34,11 @@
 | 
				
			||||||
  {#if config.freeform.inline}
 | 
					  {#if config.freeform.inline}
 | 
				
			||||||
    <Inline key={config.freeform.key} {tags} template={config.render}>
 | 
					    <Inline key={config.freeform.key} {tags} template={config.render}>
 | 
				
			||||||
      <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
 | 
					      <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
 | 
				
			||||||
                      type={config.freeform.type} {value}></ValidatedInput>
 | 
					                      type={config.freeform.type} {placeholder} {value}></ValidatedInput>
 | 
				
			||||||
    </Inline>
 | 
					    </Inline>
 | 
				
			||||||
  {:else}
 | 
					  {:else}
 | 
				
			||||||
    <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
 | 
					    <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")}
 | 
				
			||||||
                    type={config.freeform.type} {value}></ValidatedInput>
 | 
					                    type={config.freeform.type} {placeholder} {value}></ValidatedInput>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
  <InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value}/>
 | 
					  <InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value}/>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,16 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
    import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
 | 
					    import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
 | 
				
			||||||
  import { UIEventSource } from "../../../Logic/UIEventSource";
 | 
					    import {UIEventSource} from "../../../Logic/UIEventSource";
 | 
				
			||||||
  import type { Feature } from "geojson";
 | 
					    import type {Feature} from "geojson";
 | 
				
			||||||
  import type { SpecialVisualizationState } from "../../SpecialVisualization";
 | 
					    import type {SpecialVisualizationState} from "../../SpecialVisualization";
 | 
				
			||||||
    import TagRenderingAnswer from "./TagRenderingAnswer.svelte";
 | 
					    import TagRenderingAnswer from "./TagRenderingAnswer.svelte";
 | 
				
			||||||
  import { PencilAltIcon } from "@rgossiaux/svelte-heroicons/solid";
 | 
					    import {PencilAltIcon} from "@rgossiaux/svelte-heroicons/solid";
 | 
				
			||||||
    import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
 | 
					    import TagRenderingQuestion from "./TagRenderingQuestion.svelte";
 | 
				
			||||||
  import { onDestroy } from "svelte";
 | 
					    import {onDestroy} from "svelte";
 | 
				
			||||||
    import Tr from "../../Base/Tr.svelte";
 | 
					    import Tr from "../../Base/Tr.svelte";
 | 
				
			||||||
    import Translations from "../../i18n/Translations.js";
 | 
					    import Translations from "../../i18n/Translations.js";
 | 
				
			||||||
    import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
 | 
					    import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
 | 
				
			||||||
 | 
					    import {Utils} from "../../../Utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let config: TagRenderingConfig;
 | 
					    export let config: TagRenderingConfig;
 | 
				
			||||||
    export let tags: UIEventSource<Record<string, string>>;
 | 
					    export let tags: UIEventSource<Record<string, string>>;
 | 
				
			||||||
| 
						 | 
					@ -17,6 +18,8 @@
 | 
				
			||||||
    export let state: SpecialVisualizationState;
 | 
					    export let state: SpecialVisualizationState;
 | 
				
			||||||
    export let layer: LayerConfig;
 | 
					    export let layer: LayerConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export let editingEnabled = state.featureSwitchUserbadge
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let highlightedRendering: UIEventSource<string> = undefined;
 | 
					    export let highlightedRendering: UIEventSource<string> = undefined;
 | 
				
			||||||
    export let showQuestionIfUnknown: boolean = false;
 | 
					    export let showQuestionIfUnknown: boolean = false;
 | 
				
			||||||
    let editMode = false;
 | 
					    let editMode = false;
 | 
				
			||||||
| 
						 | 
					@ -25,7 +28,20 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let htmlElem: HTMLElement;
 | 
					    let htmlElem: HTMLBaseElement;
 | 
				
			||||||
 | 
					    $: {
 | 
				
			||||||
 | 
					        if (editMode && htmlElem !== undefined) {
 | 
				
			||||||
 | 
					            // EditMode switched to true, so the person wants to make a change
 | 
				
			||||||
 | 
					            // Make sure that the question is in the scrollview!
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Some delay is applied to give Svelte the time to render the _question_
 | 
				
			||||||
 | 
					            window.setTimeout(() => {
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Utils.scrollIntoView(htmlElem)
 | 
				
			||||||
 | 
					            }, 50)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const _htmlElement = new UIEventSource<HTMLElement>(undefined);
 | 
					    const _htmlElement = new UIEventSource<HTMLElement>(undefined);
 | 
				
			||||||
    $: _htmlElement.setData(htmlElem);
 | 
					    $: _htmlElement.setData(htmlElem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,22 +69,22 @@
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div bind:this={htmlElem}>
 | 
					<div bind:this={htmlElem}>
 | 
				
			||||||
  {#if config.question}
 | 
					    {#if config.question && $editingEnabled}
 | 
				
			||||||
        {#if editMode}
 | 
					        {#if editMode}
 | 
				
			||||||
            <TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
 | 
					            <TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
 | 
				
			||||||
                <button slot="cancel" on:click={() => {editMode = false}}>
 | 
					                <button slot="cancel" on:click={() => {editMode = false}}>
 | 
				
			||||||
          <Tr t={Translations.t.general.cancel} />
 | 
					                    <Tr t={Translations.t.general.cancel}/>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
            </TagRenderingQuestion>
 | 
					            </TagRenderingQuestion>
 | 
				
			||||||
        {:else}
 | 
					        {:else}
 | 
				
			||||||
            <div class="flex justify-between">
 | 
					            <div class="flex justify-between">
 | 
				
			||||||
        <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
 | 
					                <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
 | 
				
			||||||
                <button on:click={() => {editMode = true}} class="shrink-0 w-6 h-6 rounded-full subtle-background p-1">
 | 
					                <button on:click={() => {editMode = true}} class="shrink-0 w-6 h-6 rounded-full subtle-background p-1">
 | 
				
			||||||
          <PencilAltIcon></PencilAltIcon>
 | 
					                    <PencilAltIcon/>
 | 
				
			||||||
                </button>
 | 
					                </button>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
    {:else }
 | 
					    {:else }
 | 
				
			||||||
    <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
 | 
					        <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,8 @@
 | 
				
			||||||
    import SpecialTranslation from "./SpecialTranslation.svelte";
 | 
					    import SpecialTranslation from "./SpecialTranslation.svelte";
 | 
				
			||||||
    import type {SpecialVisualizationState} from "../../SpecialVisualization";
 | 
					    import type {SpecialVisualizationState} from "../../SpecialVisualization";
 | 
				
			||||||
    import type {Feature} from "geojson";
 | 
					    import type {Feature} from "geojson";
 | 
				
			||||||
    import {Store, UIEventSource} from "../../../Logic/UIEventSource";
 | 
					    import {UIEventSource} from "../../../Logic/UIEventSource";
 | 
				
			||||||
    import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
 | 
					    import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
 | 
				
			||||||
    import Locale from "../../i18n/Locale";
 | 
					 | 
				
			||||||
    import {onDestroy} from "svelte";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    export let selectedElement: Feature
 | 
					    export let selectedElement: Feature
 | 
				
			||||||
    export let tags: UIEventSource<Record<string, string>>;
 | 
					    export let tags: UIEventSource<Record<string, string>>;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -131,7 +131,6 @@
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        {#if config.mappings?.length >= 8}
 | 
					        {#if config.mappings?.length >= 8}
 | 
				
			||||||
            <div class="flex w-full">
 | 
					            <div class="flex w-full">
 | 
				
			||||||
                <img src="./assets/svg/search.svg" class="w-6 h-6"/>
 | 
					                <img src="./assets/svg/search.svg" class="w-6 h-6"/>
 | 
				
			||||||
| 
						 | 
					@ -139,7 +138,6 @@
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
        {#if config.freeform?.key && !(mappings?.length > 0)}
 | 
					        {#if config.freeform?.key && !(mappings?.length > 0)}
 | 
				
			||||||
            <!-- There are no options to choose from, simply show the input element: fill out the text field -->
 | 
					            <!-- There are no options to choose from, simply show the input element: fill out the text field -->
 | 
				
			||||||
            <FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/>
 | 
					            <FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,80 +0,0 @@
 | 
				
			||||||
import { UIEventSource } from "../../Logic/UIEventSource"
 | 
					 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					 | 
				
			||||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
					 | 
				
			||||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
 | 
					 | 
				
			||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
					 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					 | 
				
			||||||
import Img from "../Base/Img"
 | 
					 | 
				
			||||||
import { SpecialVisualizationState } from "../SpecialVisualization"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/***
 | 
					 | 
				
			||||||
 * Displays the correct value for a known tagrendering
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export default class TagRenderingAnswer extends VariableUiElement {
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        tagsSource: UIEventSource<any>,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        state: SpecialVisualizationState,
 | 
					 | 
				
			||||||
        contentClasses: string = "",
 | 
					 | 
				
			||||||
        contentStyle: string = "",
 | 
					 | 
				
			||||||
        options?: {
 | 
					 | 
				
			||||||
            specialViz: Map<string, BaseUIElement>
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        if (configuration === undefined) {
 | 
					 | 
				
			||||||
            throw "Trying to generate a tagRenderingAnswer without configuration..."
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        UIEventSource
 | 
					 | 
				
			||||||
        if (tagsSource === undefined) {
 | 
					 | 
				
			||||||
            throw "Trying to generate a tagRenderingAnswer without tagSource..."
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        super(
 | 
					 | 
				
			||||||
            tagsSource
 | 
					 | 
				
			||||||
                .map((tags) => {
 | 
					 | 
				
			||||||
                    if (tags === undefined) {
 | 
					 | 
				
			||||||
                        return undefined
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (configuration.condition) {
 | 
					 | 
				
			||||||
                        if (!configuration.condition.matchesProperties(tags)) {
 | 
					 | 
				
			||||||
                            return undefined
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const trs = Utils.NoNull(configuration.GetRenderValues(tags))
 | 
					 | 
				
			||||||
                    if (trs.length === 0) {
 | 
					 | 
				
			||||||
                        return undefined
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const valuesToRender: BaseUIElement[] = trs.map((tr) => {
 | 
					 | 
				
			||||||
                        const text = new SubstitutedTranslation(
 | 
					 | 
				
			||||||
                            tr.then,
 | 
					 | 
				
			||||||
                            tagsSource,
 | 
					 | 
				
			||||||
                            state,
 | 
					 | 
				
			||||||
                            options?.specialViz
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                        if (tr.icon === undefined) {
 | 
					 | 
				
			||||||
                            return text
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        return new Combine([
 | 
					 | 
				
			||||||
                            new Img(tr.icon).SetClass("mapping-icon-" + (tr.iconClass ?? "small")),
 | 
					 | 
				
			||||||
                            text,
 | 
					 | 
				
			||||||
                        ]).SetClass("flex items-center")
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                    if (valuesToRender.length === 1) {
 | 
					 | 
				
			||||||
                        return valuesToRender[0]
 | 
					 | 
				
			||||||
                    } else if (valuesToRender.length > 1) {
 | 
					 | 
				
			||||||
                        return new Combine(valuesToRender).SetClass("flex flex-col")
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return undefined
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .map((element: BaseUIElement) =>
 | 
					 | 
				
			||||||
                    element?.SetClass(contentClasses)?.SetStyle(contentStyle)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.SetClass("flex items-center flex-row text-lg link-underline")
 | 
					 | 
				
			||||||
        this.SetStyle("word-wrap: anywhere;")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,634 +0,0 @@
 | 
				
			||||||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
 | 
					 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					 | 
				
			||||||
import { InputElement, ReadonlyInputElement } from "../Input/InputElement"
 | 
					 | 
				
			||||||
import { FixedInputElement } from "../Input/FixedInputElement"
 | 
					 | 
				
			||||||
import { RadioButton } from "../Input/RadioButton"
 | 
					 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					 | 
				
			||||||
import CheckBoxes from "../Input/Checkboxes"
 | 
					 | 
				
			||||||
import InputElementMap from "../Input/InputElementMap"
 | 
					 | 
				
			||||||
import { SaveButton } from "./SaveButton"
 | 
					 | 
				
			||||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
					 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					 | 
				
			||||||
import { Translation } from "../i18n/Translation"
 | 
					 | 
				
			||||||
import { SubstitutedTranslation } from "../SubstitutedTranslation"
 | 
					 | 
				
			||||||
import { TagsFilter } from "../../Logic/Tags/TagsFilter"
 | 
					 | 
				
			||||||
import { Tag } from "../../Logic/Tags/Tag"
 | 
					 | 
				
			||||||
import { And } from "../../Logic/Tags/And"
 | 
					 | 
				
			||||||
import { TagUtils, UploadableTag } from "../../Logic/Tags/TagUtils"
 | 
					 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					 | 
				
			||||||
import { DropDown } from "../Input/DropDown"
 | 
					 | 
				
			||||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
 | 
					 | 
				
			||||||
import TagRenderingConfig, { Mapping } from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
					 | 
				
			||||||
import { Unit } from "../../Models/Unit"
 | 
					 | 
				
			||||||
import VariableInputElement from "../Input/VariableInputElement"
 | 
					 | 
				
			||||||
import Toggle from "../Input/Toggle"
 | 
					 | 
				
			||||||
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
 | 
					 | 
				
			||||||
import Title from "../Base/Title"
 | 
					 | 
				
			||||||
import { GeoOperations } from "../../Logic/GeoOperations"
 | 
					 | 
				
			||||||
import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
 | 
					 | 
				
			||||||
import { OsmTags } from "../../Models/OsmFeature"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @deprecated: getting stripped and getting ported
 | 
					 | 
				
			||||||
 * Shows the question element.
 | 
					 | 
				
			||||||
 * Note that the value _migh_ already be known, e.g. when selected or when changing the value
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export default class TagRenderingQuestion extends Combine {
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        tags: UIEventSource<Record<string, string> & { id: string }>,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        state?: FeaturePipelineState,
 | 
					 | 
				
			||||||
        options?: {
 | 
					 | 
				
			||||||
            units?: Unit[]
 | 
					 | 
				
			||||||
            afterSave?: () => void
 | 
					 | 
				
			||||||
            cancelButton?: BaseUIElement
 | 
					 | 
				
			||||||
            saveButtonConstr?: (src: Store<TagsFilter>) => BaseUIElement
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        const applicableMappingsSrc = Stores.ListStabilized(
 | 
					 | 
				
			||||||
            tags.map((tags) => {
 | 
					 | 
				
			||||||
                const applicableMappings: Mapping[] = []
 | 
					 | 
				
			||||||
                for (const mapping of configuration.mappings ?? []) {
 | 
					 | 
				
			||||||
                    if (mapping.hideInAnswer === true) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (mapping.hideInAnswer === false || mapping.hideInAnswer === undefined) {
 | 
					 | 
				
			||||||
                        applicableMappings.push(mapping)
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const condition = <TagsFilter>mapping.hideInAnswer
 | 
					 | 
				
			||||||
                    const isShown = !condition.matchesProperties(tags)
 | 
					 | 
				
			||||||
                    if (isShown) {
 | 
					 | 
				
			||||||
                        applicableMappings.push(mapping)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return applicableMappings
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (configuration === undefined) {
 | 
					 | 
				
			||||||
            throw "A question is needed for a question visualization"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        options = options ?? {}
 | 
					 | 
				
			||||||
        const applicableUnit = (options.units ?? []).filter((unit) =>
 | 
					 | 
				
			||||||
            unit.isApplicableToKey(configuration.freeform?.key)
 | 
					 | 
				
			||||||
        )[0]
 | 
					 | 
				
			||||||
        const question = new Title(
 | 
					 | 
				
			||||||
            new SubstitutedTranslation(configuration.question, tags, state).SetClass(
 | 
					 | 
				
			||||||
                "question-text"
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            3
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        let questionHint = undefined
 | 
					 | 
				
			||||||
        if (configuration.questionhint !== undefined) {
 | 
					 | 
				
			||||||
            questionHint = new SubstitutedTranslation(
 | 
					 | 
				
			||||||
                configuration.questionhint,
 | 
					 | 
				
			||||||
                tags,
 | 
					 | 
				
			||||||
                state
 | 
					 | 
				
			||||||
            ).SetClass("font-bold subtle")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const feedback = new UIEventSource<Translation>(undefined)
 | 
					 | 
				
			||||||
        const inputElement: ReadonlyInputElement<UploadableTag> = new VariableInputElement(
 | 
					 | 
				
			||||||
            applicableMappingsSrc.map((applicableMappings) => {
 | 
					 | 
				
			||||||
                return TagRenderingQuestion.GenerateInputElement(
 | 
					 | 
				
			||||||
                    state,
 | 
					 | 
				
			||||||
                    configuration,
 | 
					 | 
				
			||||||
                    applicableMappings,
 | 
					 | 
				
			||||||
                    applicableUnit,
 | 
					 | 
				
			||||||
                    tags,
 | 
					 | 
				
			||||||
                    feedback
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (options.saveButtonConstr === undefined) {
 | 
					 | 
				
			||||||
            const save = () => {
 | 
					 | 
				
			||||||
                const selection = TagUtils.FlattenMultiAnswer(
 | 
					 | 
				
			||||||
                    TagUtils.FlattenAnd(inputElement.GetValue().data, tags.data)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                if (selection) {
 | 
					 | 
				
			||||||
                    ;(state?.changes)
 | 
					 | 
				
			||||||
                        .applyAction(
 | 
					 | 
				
			||||||
                            new ChangeTagAction(tags.data.id, selection, tags.data, {
 | 
					 | 
				
			||||||
                                theme: state?.layoutToUse?.id ?? "unkown",
 | 
					 | 
				
			||||||
                                changeType: "answer",
 | 
					 | 
				
			||||||
                            })
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                        .then((_) => {
 | 
					 | 
				
			||||||
                            console.log("Tagchanges applied")
 | 
					 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
                    if (options.afterSave) {
 | 
					 | 
				
			||||||
                        options.afterSave()
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            options.saveButtonConstr = (v) => new SaveButton(v, state?.osmConnection).onClick(save)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const saveButton = new Combine([options.saveButtonConstr(inputElement.GetValue())])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        super([
 | 
					 | 
				
			||||||
            question,
 | 
					 | 
				
			||||||
            questionHint,
 | 
					 | 
				
			||||||
            inputElement,
 | 
					 | 
				
			||||||
            new VariableUiElement(
 | 
					 | 
				
			||||||
                feedback.map((t) =>
 | 
					 | 
				
			||||||
                    t
 | 
					 | 
				
			||||||
                        ?.SetStyle("padding-left: 0.75rem; padding-right: 0.75rem")
 | 
					 | 
				
			||||||
                        ?.SetClass("alert flex")
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            new Combine([options.cancelButton, saveButton]).SetClass(
 | 
					 | 
				
			||||||
                "flex justify-end flex-wrap-reverse"
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            new Toggle(
 | 
					 | 
				
			||||||
                Translations.t.general.testing.SetClass("block alert"),
 | 
					 | 
				
			||||||
                undefined,
 | 
					 | 
				
			||||||
                state?.featureSwitchIsTesting
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        ])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.SetClass("question disable-links")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static GenerateInputElement(
 | 
					 | 
				
			||||||
        state: FeaturePipelineState,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        applicableMappings: Mapping[],
 | 
					 | 
				
			||||||
        applicableUnit: Unit,
 | 
					 | 
				
			||||||
        tagsSource: UIEventSource<any>,
 | 
					 | 
				
			||||||
        feedback: UIEventSource<Translation>
 | 
					 | 
				
			||||||
    ): ReadonlyInputElement<UploadableTag> {
 | 
					 | 
				
			||||||
        const hasImages = applicableMappings.findIndex((mapping) => mapping.icon !== undefined) >= 0
 | 
					 | 
				
			||||||
        let inputEls: InputElement<UploadableTag>[]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const ifNotsPresent = applicableMappings.some((mapping) => mapping.ifnot !== undefined)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            applicableMappings.length > 8 &&
 | 
					 | 
				
			||||||
            (configuration.freeform?.type === undefined ||
 | 
					 | 
				
			||||||
                configuration.freeform?.type === "string") &&
 | 
					 | 
				
			||||||
            (!configuration.multiAnswer || configuration.freeform === undefined)
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            return TagRenderingQuestion.GenerateSearchableSelector(
 | 
					 | 
				
			||||||
                state,
 | 
					 | 
				
			||||||
                configuration,
 | 
					 | 
				
			||||||
                applicableMappings,
 | 
					 | 
				
			||||||
                tagsSource
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // FreeForm input will be undefined if not present; will already contain a special input element if applicable
 | 
					 | 
				
			||||||
        const ff = TagRenderingQuestion.GenerateFreeform(
 | 
					 | 
				
			||||||
            state,
 | 
					 | 
				
			||||||
            configuration,
 | 
					 | 
				
			||||||
            applicableUnit,
 | 
					 | 
				
			||||||
            tagsSource,
 | 
					 | 
				
			||||||
            feedback
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function allIfNotsExcept(excludeIndex: number): UploadableTag[] {
 | 
					 | 
				
			||||||
            if (configuration.mappings === undefined || configuration.mappings.length === 0) {
 | 
					 | 
				
			||||||
                return undefined
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (!ifNotsPresent) {
 | 
					 | 
				
			||||||
                return []
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (configuration.multiAnswer) {
 | 
					 | 
				
			||||||
                // The multianswer will do the ifnot configuration themself
 | 
					 | 
				
			||||||
                return []
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const negativeMappings = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for (let i = 0; i < applicableMappings.length; i++) {
 | 
					 | 
				
			||||||
                const mapping = applicableMappings[i]
 | 
					 | 
				
			||||||
                if (i === excludeIndex || mapping.ifnot === undefined) {
 | 
					 | 
				
			||||||
                    continue
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                negativeMappings.push(mapping.ifnot)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return Utils.NoNull(negativeMappings)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            applicableMappings.length < 8 ||
 | 
					 | 
				
			||||||
            configuration.multiAnswer ||
 | 
					 | 
				
			||||||
            (hasImages && applicableMappings.length < 16) ||
 | 
					 | 
				
			||||||
            ifNotsPresent
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            inputEls = (applicableMappings ?? []).map((mapping, i) =>
 | 
					 | 
				
			||||||
                TagRenderingQuestion.GenerateMappingElement(
 | 
					 | 
				
			||||||
                    state,
 | 
					 | 
				
			||||||
                    tagsSource,
 | 
					 | 
				
			||||||
                    mapping,
 | 
					 | 
				
			||||||
                    allIfNotsExcept(i)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            inputEls = Utils.NoNull(inputEls)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            const dropdown: InputElement<UploadableTag> = new DropDown(
 | 
					 | 
				
			||||||
                "",
 | 
					 | 
				
			||||||
                applicableMappings.map((mapping, i) => {
 | 
					 | 
				
			||||||
                    return {
 | 
					 | 
				
			||||||
                        value: new And([mapping.if, ...allIfNotsExcept(i)]),
 | 
					 | 
				
			||||||
                        shown: mapping.then.Subs(tagsSource.data),
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (ff == undefined) {
 | 
					 | 
				
			||||||
                return dropdown
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                inputEls = [dropdown]
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (inputEls.length == 0) {
 | 
					 | 
				
			||||||
            if (ff === undefined) {
 | 
					 | 
				
			||||||
                throw "Error: could not generate a question: freeform and all mappings are undefined"
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return ff
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (ff) {
 | 
					 | 
				
			||||||
            inputEls.push(ff)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (configuration.multiAnswer) {
 | 
					 | 
				
			||||||
            return TagRenderingQuestion.GenerateMultiAnswer(
 | 
					 | 
				
			||||||
                configuration,
 | 
					 | 
				
			||||||
                inputEls,
 | 
					 | 
				
			||||||
                ff,
 | 
					 | 
				
			||||||
                applicableMappings.map((mp) => mp.ifnot)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            return new RadioButton(inputEls, { selectFirstAsDefault: false })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static MappingToPillValue(
 | 
					 | 
				
			||||||
        applicableMappings: Mapping[],
 | 
					 | 
				
			||||||
        tagsSource: UIEventSource<OsmTags>,
 | 
					 | 
				
			||||||
        state: FeaturePipelineState
 | 
					 | 
				
			||||||
    ): {
 | 
					 | 
				
			||||||
        show: BaseUIElement
 | 
					 | 
				
			||||||
        value: number
 | 
					 | 
				
			||||||
        mainTerm: Record<string, string>
 | 
					 | 
				
			||||||
        searchTerms?: Record<string, string[]>
 | 
					 | 
				
			||||||
        original: Mapping
 | 
					 | 
				
			||||||
        hasPriority?: Store<boolean>
 | 
					 | 
				
			||||||
    }[] {
 | 
					 | 
				
			||||||
        const values: {
 | 
					 | 
				
			||||||
            show: BaseUIElement
 | 
					 | 
				
			||||||
            value: number
 | 
					 | 
				
			||||||
            mainTerm: Record<string, string>
 | 
					 | 
				
			||||||
            searchTerms?: Record<string, string[]>
 | 
					 | 
				
			||||||
            original: Mapping
 | 
					 | 
				
			||||||
            hasPriority?: Store<boolean>
 | 
					 | 
				
			||||||
        }[] = []
 | 
					 | 
				
			||||||
        const addIcons = applicableMappings.some((m) => m.icon !== undefined)
 | 
					 | 
				
			||||||
        for (let i = 0; i < applicableMappings.length; i++) {
 | 
					 | 
				
			||||||
            const mapping = applicableMappings[i]
 | 
					 | 
				
			||||||
            const tr = mapping.then.Subs(tagsSource.data)
 | 
					 | 
				
			||||||
            const patchedMapping = <Mapping>{
 | 
					 | 
				
			||||||
                ...mapping,
 | 
					 | 
				
			||||||
                iconClass: mapping.iconClass ?? `small-height`,
 | 
					 | 
				
			||||||
                icon: mapping.icon ?? (addIcons ? "./assets/svg/none.svg" : undefined),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const fancy = TagRenderingQuestion.GenerateMappingContent(
 | 
					 | 
				
			||||||
                patchedMapping,
 | 
					 | 
				
			||||||
                tagsSource,
 | 
					 | 
				
			||||||
                state
 | 
					 | 
				
			||||||
            ).SetClass("normal-background")
 | 
					 | 
				
			||||||
            values.push({
 | 
					 | 
				
			||||||
                show: fancy,
 | 
					 | 
				
			||||||
                value: i,
 | 
					 | 
				
			||||||
                mainTerm: tr.translations,
 | 
					 | 
				
			||||||
                searchTerms: mapping.searchTerms,
 | 
					 | 
				
			||||||
                original: mapping,
 | 
					 | 
				
			||||||
                hasPriority: tagsSource.map((tags) => mapping.priorityIf?.matchesProperties(tags)),
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return values
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static GenerateSearchableSelector(
 | 
					 | 
				
			||||||
        state: FeaturePipelineState,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        applicableMappings: Mapping[],
 | 
					 | 
				
			||||||
        tagsSource: UIEventSource<OsmTags>,
 | 
					 | 
				
			||||||
        options?: {
 | 
					 | 
				
			||||||
            search: UIEventSource<string>
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    ): InputElement<UploadableTag> {
 | 
					 | 
				
			||||||
        const values = TagRenderingQuestion.MappingToPillValue(
 | 
					 | 
				
			||||||
            applicableMappings,
 | 
					 | 
				
			||||||
            tagsSource,
 | 
					 | 
				
			||||||
            state
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const searchValue: UIEventSource<string> =
 | 
					 | 
				
			||||||
            options?.search ?? new UIEventSource<string>(undefined)
 | 
					 | 
				
			||||||
        const ff = configuration.freeform
 | 
					 | 
				
			||||||
        let onEmpty: BaseUIElement = undefined
 | 
					 | 
				
			||||||
        if (ff !== undefined) {
 | 
					 | 
				
			||||||
            onEmpty = new VariableUiElement(
 | 
					 | 
				
			||||||
                searchValue.map((search) => configuration.render.Subs({ [ff.key]: search }))
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const mode = configuration.multiAnswer ? "select-many" : "select-one"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const classes = "h-64 overflow-scroll"
 | 
					 | 
				
			||||||
        const presetSearch = new SearchablePillsSelector<number>(values, {
 | 
					 | 
				
			||||||
            mode,
 | 
					 | 
				
			||||||
            searchValue,
 | 
					 | 
				
			||||||
            onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"),
 | 
					 | 
				
			||||||
            searchAreaClass: classes,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        const fallbackTag = searchValue.map((s) => {
 | 
					 | 
				
			||||||
            if (s === undefined || ff?.key === undefined) {
 | 
					 | 
				
			||||||
                return undefined
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return new Tag(ff.key, s)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        return new InputElementMap<number[], And>(
 | 
					 | 
				
			||||||
            presetSearch,
 | 
					 | 
				
			||||||
            (x0, x1) => {
 | 
					 | 
				
			||||||
                if (x0 == x1) {
 | 
					 | 
				
			||||||
                    return true
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (x0 === undefined || x1 === undefined) {
 | 
					 | 
				
			||||||
                    return false
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (x0.and.length !== x1.and.length) {
 | 
					 | 
				
			||||||
                    return false
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                for (let i = 0; i < x0.and.length; i++) {
 | 
					 | 
				
			||||||
                    if (x1.and[i] != x0.and[i]) {
 | 
					 | 
				
			||||||
                        return false
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return true
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (selected) => {
 | 
					 | 
				
			||||||
                if (
 | 
					 | 
				
			||||||
                    ff !== undefined &&
 | 
					 | 
				
			||||||
                    searchValue.data?.length > 0 &&
 | 
					 | 
				
			||||||
                    !presetSearch.someMatchFound.data
 | 
					 | 
				
			||||||
                ) {
 | 
					 | 
				
			||||||
                    const t = fallbackTag.data
 | 
					 | 
				
			||||||
                    if (ff.addExtraTags) {
 | 
					 | 
				
			||||||
                        return new And([t, ...ff.addExtraTags])
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return new And([t])
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (selected === undefined || selected.length == 0) {
 | 
					 | 
				
			||||||
                    return undefined
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const tfs = Utils.NoNull(
 | 
					 | 
				
			||||||
                    applicableMappings.map((mapping, i) => {
 | 
					 | 
				
			||||||
                        if (selected.indexOf(i) >= 0) {
 | 
					 | 
				
			||||||
                            return mapping.if
 | 
					 | 
				
			||||||
                        } else {
 | 
					 | 
				
			||||||
                            return mapping.ifnot
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                return new And(tfs)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (tf) => {
 | 
					 | 
				
			||||||
                if (tf === undefined) {
 | 
					 | 
				
			||||||
                    return []
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const selected: number[] = []
 | 
					 | 
				
			||||||
                for (let i = 0; i < applicableMappings.length; i++) {
 | 
					 | 
				
			||||||
                    const mapping = applicableMappings[i]
 | 
					 | 
				
			||||||
                    if (tf.and.some((t) => mapping.if == t)) {
 | 
					 | 
				
			||||||
                        selected.push(i)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return selected
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            [searchValue, presetSearch.someMatchFound]
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static GenerateMultiAnswer(
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        elements: InputElement<UploadableTag>[],
 | 
					 | 
				
			||||||
        freeformField: InputElement<UploadableTag>,
 | 
					 | 
				
			||||||
        ifNotSelected: UploadableTag[]
 | 
					 | 
				
			||||||
    ): InputElement<UploadableTag> {
 | 
					 | 
				
			||||||
        const checkBoxes = new CheckBoxes(elements)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const inputEl = new InputElementMap<number[], UploadableTag>(
 | 
					 | 
				
			||||||
            checkBoxes,
 | 
					 | 
				
			||||||
            (t0, t1) => {
 | 
					 | 
				
			||||||
                return t0?.shadows(t1) ?? false
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (indices) => {
 | 
					 | 
				
			||||||
                if (indices.length === 0) {
 | 
					 | 
				
			||||||
                    return undefined
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const tags: UploadableTag[] = indices.map((i) => elements[i].GetValue().data)
 | 
					 | 
				
			||||||
                const oppositeTags: UploadableTag[] = []
 | 
					 | 
				
			||||||
                for (let i = 0; i < ifNotSelected.length; i++) {
 | 
					 | 
				
			||||||
                    if (indices.indexOf(i) >= 0) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const notSelected = ifNotSelected[i]
 | 
					 | 
				
			||||||
                    if (notSelected === undefined) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    oppositeTags.push(notSelected)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                tags.push(TagUtils.FlattenMultiAnswer(oppositeTags))
 | 
					 | 
				
			||||||
                return TagUtils.FlattenMultiAnswer(tags)
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            (tags: UploadableTag) => {
 | 
					 | 
				
			||||||
                // {key --> values[]}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const presentTags = TagUtils.SplitKeys([tags])
 | 
					 | 
				
			||||||
                const indices: number[] = []
 | 
					 | 
				
			||||||
                // We also collect the values that have to be added to the freeform field
 | 
					 | 
				
			||||||
                let freeformExtras: string[] = []
 | 
					 | 
				
			||||||
                if (configuration.freeform?.key) {
 | 
					 | 
				
			||||||
                    freeformExtras = [...(presentTags[configuration.freeform.key] ?? [])]
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for (let j = 0; j < elements.length; j++) {
 | 
					 | 
				
			||||||
                    const inputElement = elements[j]
 | 
					 | 
				
			||||||
                    if (inputElement === freeformField) {
 | 
					 | 
				
			||||||
                        continue
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    const val = inputElement.GetValue()
 | 
					 | 
				
			||||||
                    const neededTags = TagUtils.SplitKeys([val.data])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // if every 'neededKeys'-value is present in presentKeys, we have a match and enable the index
 | 
					 | 
				
			||||||
                    if (TagUtils.AllKeysAreContained(presentTags, neededTags)) {
 | 
					 | 
				
			||||||
                        indices.push(j)
 | 
					 | 
				
			||||||
                        if (freeformExtras.length > 0) {
 | 
					 | 
				
			||||||
                            const freeformsToRemove: string[] =
 | 
					 | 
				
			||||||
                                neededTags[configuration.freeform.key] ?? []
 | 
					 | 
				
			||||||
                            for (const toRm of freeformsToRemove) {
 | 
					 | 
				
			||||||
                                const i = freeformExtras.indexOf(toRm)
 | 
					 | 
				
			||||||
                                if (i >= 0) {
 | 
					 | 
				
			||||||
                                    freeformExtras.splice(i, 1)
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                if (freeformField) {
 | 
					 | 
				
			||||||
                    if (freeformExtras.length > 0) {
 | 
					 | 
				
			||||||
                        freeformField
 | 
					 | 
				
			||||||
                            .GetValue()
 | 
					 | 
				
			||||||
                            .setData(new Tag(configuration.freeform.key, freeformExtras.join(";")))
 | 
					 | 
				
			||||||
                        indices.push(elements.indexOf(freeformField))
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        freeformField.GetValue().setData(undefined)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return indices
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            elements.map((el) => el.GetValue())
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        freeformField?.GetValue()?.addCallbackAndRun((value) => {
 | 
					 | 
				
			||||||
            // The list of indices of the selected elements
 | 
					 | 
				
			||||||
            const es = checkBoxes.GetValue()
 | 
					 | 
				
			||||||
            const i = elements.length - 1
 | 
					 | 
				
			||||||
            // The actual index of the freeform-element
 | 
					 | 
				
			||||||
            const index = es.data.indexOf(i)
 | 
					 | 
				
			||||||
            if (value === undefined) {
 | 
					 | 
				
			||||||
                // No data is set in the freeform text field; so we delete the checkmark if it is selected
 | 
					 | 
				
			||||||
                if (index >= 0) {
 | 
					 | 
				
			||||||
                    es.data.splice(index, 1)
 | 
					 | 
				
			||||||
                    es.ping()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else if (index < 0) {
 | 
					 | 
				
			||||||
                // There is data defined in the checkmark, but the checkmark isn't checked, so we check it
 | 
					 | 
				
			||||||
                // This is of course because the data changed
 | 
					 | 
				
			||||||
                es.data.push(i)
 | 
					 | 
				
			||||||
                es.ping()
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return inputEl
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Generates a (Fixed) input element for this mapping.
 | 
					 | 
				
			||||||
     * Note that the mapping might hide itself if the condition is not met anymore.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * Returns: [the element itself, the value to select if not selected. The contents of this UIEventSource might swap to undefined if the conditions to show the answer are unmet]
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static GenerateMappingElement(
 | 
					 | 
				
			||||||
        state,
 | 
					 | 
				
			||||||
        tagsSource: UIEventSource<any>,
 | 
					 | 
				
			||||||
        mapping: Mapping,
 | 
					 | 
				
			||||||
        ifNot?: UploadableTag[]
 | 
					 | 
				
			||||||
    ): InputElement<UploadableTag> {
 | 
					 | 
				
			||||||
        let tagging: UploadableTag = mapping.if
 | 
					 | 
				
			||||||
        if (ifNot !== undefined) {
 | 
					 | 
				
			||||||
            tagging = new And([mapping.if, ...ifNot])
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (mapping.addExtraTags) {
 | 
					 | 
				
			||||||
            tagging = new And([tagging, ...mapping.addExtraTags])
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return new FixedInputElement(
 | 
					 | 
				
			||||||
            TagRenderingQuestion.GenerateMappingContent(mapping, tagsSource, state),
 | 
					 | 
				
			||||||
            tagging,
 | 
					 | 
				
			||||||
            (t0, t1) => t1.shadows(t0)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static GenerateMappingContent(
 | 
					 | 
				
			||||||
        mapping: Mapping,
 | 
					 | 
				
			||||||
        tagsSource: UIEventSource<any>,
 | 
					 | 
				
			||||||
        state: FeaturePipelineState
 | 
					 | 
				
			||||||
    ): BaseUIElement {
 | 
					 | 
				
			||||||
        return undefined
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static GenerateFreeform(
 | 
					 | 
				
			||||||
        state: FeaturePipelineState,
 | 
					 | 
				
			||||||
        configuration: TagRenderingConfig,
 | 
					 | 
				
			||||||
        applicableUnit: Unit,
 | 
					 | 
				
			||||||
        tags: UIEventSource<any>,
 | 
					 | 
				
			||||||
        feedback: UIEventSource<Translation>
 | 
					 | 
				
			||||||
    ): InputElement<UploadableTag> {
 | 
					 | 
				
			||||||
        const freeform = configuration.freeform
 | 
					 | 
				
			||||||
        if (freeform === undefined) {
 | 
					 | 
				
			||||||
            return undefined
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const pickString = (string: any) => {
 | 
					 | 
				
			||||||
            if (string === "" || string === undefined) {
 | 
					 | 
				
			||||||
                return undefined
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (string.length >= 255) {
 | 
					 | 
				
			||||||
                return undefined
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const tag = new Tag(freeform.key, string)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (freeform.addExtraTags === undefined) {
 | 
					 | 
				
			||||||
                return tag
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return new And([tag, ...freeform.addExtraTags])
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const toString = (tag) => {
 | 
					 | 
				
			||||||
            if (tag instanceof And) {
 | 
					 | 
				
			||||||
                for (const subtag of tag.and) {
 | 
					 | 
				
			||||||
                    if (subtag instanceof Tag && subtag.key === freeform.key) {
 | 
					 | 
				
			||||||
                        return subtag.value
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return undefined
 | 
					 | 
				
			||||||
            } else if (tag instanceof Tag) {
 | 
					 | 
				
			||||||
                return tag.value
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return undefined
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const tagsData = tags.data
 | 
					 | 
				
			||||||
        const feature = state?.allElements?.ContainingFeatures?.get(tagsData.id)
 | 
					 | 
				
			||||||
        const center = feature != undefined ? GeoOperations.centerpointCoordinates(feature) : [0, 0]
 | 
					 | 
				
			||||||
        const input: InputElement<string> = ValidatedTextField.ForType(
 | 
					 | 
				
			||||||
            configuration.freeform.type
 | 
					 | 
				
			||||||
        )?.ConstructInputElement({
 | 
					 | 
				
			||||||
            country: () => tagsData._country,
 | 
					 | 
				
			||||||
            location: [center[1], center[0]],
 | 
					 | 
				
			||||||
            mapBackgroundLayer: state?.backgroundLayer,
 | 
					 | 
				
			||||||
            unit: applicableUnit,
 | 
					 | 
				
			||||||
            args: configuration.freeform.helperArgs,
 | 
					 | 
				
			||||||
            feature,
 | 
					 | 
				
			||||||
            placeholder: configuration.freeform.placeholder,
 | 
					 | 
				
			||||||
            feedback,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Add a length check
 | 
					 | 
				
			||||||
        input?.GetValue().addCallbackD((v: string | undefined) => {
 | 
					 | 
				
			||||||
            if (v?.length >= 255) {
 | 
					 | 
				
			||||||
                feedback.setData(Translations.t.validation.tooLong.Subs({ count: v.length }))
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return new InputElementMap(
 | 
					 | 
				
			||||||
            input,
 | 
					 | 
				
			||||||
            (a, b) => a === b || (a?.shadows(b) ?? false),
 | 
					 | 
				
			||||||
            pickString,
 | 
					 | 
				
			||||||
            toString
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,59 +1,55 @@
 | 
				
			||||||
import Combine from "./Base/Combine"
 | 
					import Combine from "./Base/Combine"
 | 
				
			||||||
import { FixedUiElement } from "./Base/FixedUiElement"
 | 
					import {FixedUiElement} from "./Base/FixedUiElement"
 | 
				
			||||||
import BaseUIElement from "./BaseUIElement"
 | 
					import BaseUIElement from "./BaseUIElement"
 | 
				
			||||||
import Title from "./Base/Title"
 | 
					import Title from "./Base/Title"
 | 
				
			||||||
import Table from "./Base/Table"
 | 
					import Table from "./Base/Table"
 | 
				
			||||||
import {
 | 
					import {RenderingSpecification, SpecialVisualization, SpecialVisualizationState,} from "./SpecialVisualization"
 | 
				
			||||||
    RenderingSpecification,
 | 
					import {HistogramViz} from "./Popup/HistogramViz"
 | 
				
			||||||
    SpecialVisualization,
 | 
					import {MinimapViz} from "./Popup/MinimapViz"
 | 
				
			||||||
    SpecialVisualizationState,
 | 
					import {ShareLinkViz} from "./Popup/ShareLinkViz"
 | 
				
			||||||
} from "./SpecialVisualization"
 | 
					import {UploadToOsmViz} from "./Popup/UploadToOsmViz"
 | 
				
			||||||
import { HistogramViz } from "./Popup/HistogramViz"
 | 
					import {MultiApplyViz} from "./Popup/MultiApplyViz"
 | 
				
			||||||
import { MinimapViz } from "./Popup/MinimapViz"
 | 
					import {AddNoteCommentViz} from "./Popup/AddNoteCommentViz"
 | 
				
			||||||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
 | 
					import {PlantNetDetectionViz} from "./Popup/PlantNetDetectionViz"
 | 
				
			||||||
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
 | 
					import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton"
 | 
				
			||||||
import { MultiApplyViz } from "./Popup/MultiApplyViz"
 | 
					 | 
				
			||||||
import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"
 | 
					 | 
				
			||||||
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
 | 
					 | 
				
			||||||
import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/ImportButton"
 | 
					 | 
				
			||||||
import TagApplyButton from "./Popup/TagApplyButton"
 | 
					import TagApplyButton from "./Popup/TagApplyButton"
 | 
				
			||||||
import { CloseNoteButton } from "./Popup/CloseNoteButton"
 | 
					import {CloseNoteButton} from "./Popup/CloseNoteButton"
 | 
				
			||||||
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
 | 
					import {MapillaryLinkVis} from "./Popup/MapillaryLinkVis"
 | 
				
			||||||
import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"
 | 
					import {Store, Stores, UIEventSource} from "../Logic/UIEventSource"
 | 
				
			||||||
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
 | 
					import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
 | 
				
			||||||
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
 | 
					import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"
 | 
				
			||||||
import { ImageCarousel } from "./Image/ImageCarousel"
 | 
					import {ImageCarousel} from "./Image/ImageCarousel"
 | 
				
			||||||
import { ImageUploadFlow } from "./Image/ImageUploadFlow"
 | 
					import {ImageUploadFlow} from "./Image/ImageUploadFlow"
 | 
				
			||||||
import { VariableUiElement } from "./Base/VariableUIElement"
 | 
					import {VariableUiElement} from "./Base/VariableUIElement"
 | 
				
			||||||
import { Utils } from "../Utils"
 | 
					import {Utils} from "../Utils"
 | 
				
			||||||
import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"
 | 
					import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata"
 | 
				
			||||||
import { Translation } from "./i18n/Translation"
 | 
					import {Translation} from "./i18n/Translation"
 | 
				
			||||||
import Translations from "./i18n/Translations"
 | 
					import Translations from "./i18n/Translations"
 | 
				
			||||||
import ReviewForm from "./Reviews/ReviewForm"
 | 
					import ReviewForm from "./Reviews/ReviewForm"
 | 
				
			||||||
import ReviewElement from "./Reviews/ReviewElement"
 | 
					import ReviewElement from "./Reviews/ReviewElement"
 | 
				
			||||||
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
 | 
					import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
 | 
				
			||||||
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"
 | 
					import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"
 | 
				
			||||||
import { SubtleButton } from "./Base/SubtleButton"
 | 
					import {SubtleButton} from "./Base/SubtleButton"
 | 
				
			||||||
import Svg from "../Svg"
 | 
					import Svg from "../Svg"
 | 
				
			||||||
import { OpenIdEditor, OpenJosm } from "./BigComponents/CopyrightPanel"
 | 
					import {OpenIdEditor, OpenJosm} from "./BigComponents/CopyrightPanel"
 | 
				
			||||||
import Hash from "../Logic/Web/Hash"
 | 
					import Hash from "../Logic/Web/Hash"
 | 
				
			||||||
import NoteCommentElement from "./Popup/NoteCommentElement"
 | 
					import NoteCommentElement from "./Popup/NoteCommentElement"
 | 
				
			||||||
import ImgurUploader from "../Logic/ImageProviders/ImgurUploader"
 | 
					import ImgurUploader from "../Logic/ImageProviders/ImgurUploader"
 | 
				
			||||||
import FileSelectorButton from "./Input/FileSelectorButton"
 | 
					import FileSelectorButton from "./Input/FileSelectorButton"
 | 
				
			||||||
import { LoginToggle } from "./Popup/LoginButton"
 | 
					import {LoginToggle} from "./Popup/LoginButton"
 | 
				
			||||||
import Toggle from "./Input/Toggle"
 | 
					import Toggle from "./Input/Toggle"
 | 
				
			||||||
import { SubstitutedTranslation } from "./SubstitutedTranslation"
 | 
					import {SubstitutedTranslation} from "./SubstitutedTranslation"
 | 
				
			||||||
import List from "./Base/List"
 | 
					import List from "./Base/List"
 | 
				
			||||||
import StatisticsPanel from "./BigComponents/StatisticsPanel"
 | 
					import StatisticsPanel from "./BigComponents/StatisticsPanel"
 | 
				
			||||||
import AutoApplyButton from "./Popup/AutoApplyButton"
 | 
					import AutoApplyButton from "./Popup/AutoApplyButton"
 | 
				
			||||||
import { LanguageElement } from "./Popup/LanguageElement"
 | 
					import {LanguageElement} from "./Popup/LanguageElement"
 | 
				
			||||||
import FeatureReviews from "../Logic/Web/MangroveReviews"
 | 
					import FeatureReviews from "../Logic/Web/MangroveReviews"
 | 
				
			||||||
import Maproulette from "../Logic/Maproulette"
 | 
					import Maproulette from "../Logic/Maproulette"
 | 
				
			||||||
import SvelteUIElement from "./Base/SvelteUIElement"
 | 
					import SvelteUIElement from "./Base/SvelteUIElement"
 | 
				
			||||||
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
 | 
					import {BBoxFeatureSourceForLayer} from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
 | 
				
			||||||
import QuestionViz from "./Popup/QuestionViz"
 | 
					import QuestionViz from "./Popup/QuestionViz"
 | 
				
			||||||
import { Feature, Point } from "geojson"
 | 
					import {Feature, Point} from "geojson"
 | 
				
			||||||
import { GeoOperations } from "../Logic/GeoOperations"
 | 
					import {GeoOperations} from "../Logic/GeoOperations"
 | 
				
			||||||
import CreateNewNote from "./Popup/CreateNewNote.svelte"
 | 
					import CreateNewNote from "./Popup/CreateNewNote.svelte"
 | 
				
			||||||
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
 | 
					import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
 | 
				
			||||||
import UserProfile from "./BigComponents/UserProfile.svelte"
 | 
					import UserProfile from "./BigComponents/UserProfile.svelte"
 | 
				
			||||||
| 
						 | 
					@ -61,25 +57,21 @@ import LanguagePicker from "./LanguagePicker"
 | 
				
			||||||
import Link from "./Base/Link"
 | 
					import Link from "./Base/Link"
 | 
				
			||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
 | 
					import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
import EditableTagRendering from "./Popup/EditableTagRendering"
 | 
					import NearbyImages, {NearbyImageOptions, P4CPicture, SelectOneNearbyImage,} from "./Popup/NearbyImages"
 | 
				
			||||||
import NearbyImages, {
 | 
					import {Tag} from "../Logic/Tags/Tag"
 | 
				
			||||||
    NearbyImageOptions,
 | 
					 | 
				
			||||||
    P4CPicture,
 | 
					 | 
				
			||||||
    SelectOneNearbyImage,
 | 
					 | 
				
			||||||
} from "./Popup/NearbyImages"
 | 
					 | 
				
			||||||
import { Tag } from "../Logic/Tags/Tag"
 | 
					 | 
				
			||||||
import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction"
 | 
					import ChangeTagAction from "../Logic/Osm/Actions/ChangeTagAction"
 | 
				
			||||||
import { And } from "../Logic/Tags/And"
 | 
					import {And} from "../Logic/Tags/And"
 | 
				
			||||||
import { SaveButton } from "./Popup/SaveButton"
 | 
					import {SaveButton} from "./Popup/SaveButton"
 | 
				
			||||||
import Lazy from "./Base/Lazy"
 | 
					import Lazy from "./Base/Lazy"
 | 
				
			||||||
import { CheckBox } from "./Input/Checkboxes"
 | 
					import {CheckBox} from "./Input/Checkboxes"
 | 
				
			||||||
import Slider from "./Input/Slider"
 | 
					import Slider from "./Input/Slider"
 | 
				
			||||||
import DeleteWizard from "./Popup/DeleteWizard"
 | 
					import DeleteWizard from "./Popup/DeleteWizard"
 | 
				
			||||||
import { OsmId, OsmTags, WayId } from "../Models/OsmFeature"
 | 
					import {OsmId, OsmTags, WayId} from "../Models/OsmFeature"
 | 
				
			||||||
import MoveWizard from "./Popup/MoveWizard"
 | 
					import MoveWizard from "./Popup/MoveWizard"
 | 
				
			||||||
import SplitRoadWizard from "./Popup/SplitRoadWizard"
 | 
					import SplitRoadWizard from "./Popup/SplitRoadWizard"
 | 
				
			||||||
import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"
 | 
					import {ExportAsGpxViz} from "./Popup/ExportAsGpxViz"
 | 
				
			||||||
import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
 | 
					import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"
 | 
				
			||||||
 | 
					import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NearbyImageVis implements SpecialVisualization {
 | 
					class NearbyImageVis implements SpecialVisualization {
 | 
				
			||||||
    // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | 
					    // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | 
				
			||||||
| 
						 | 
					@ -250,22 +242,22 @@ class StealViz implements SpecialVisualization {
 | 
				
			||||||
                    return undefined
 | 
					                    return undefined
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const otherTags = state.featureProperties.getStore(featureId)
 | 
					                const otherTags = state.featureProperties.getStore(featureId)
 | 
				
			||||||
 | 
					                const otherFeature = state.indexedFeatures.featuresById.data.get(featureId);
 | 
				
			||||||
                const elements: BaseUIElement[] = []
 | 
					                const elements: BaseUIElement[] = []
 | 
				
			||||||
                for (const [layer, tagRendering] of tagRenderings) {
 | 
					                for (const [layer, tagRendering] of tagRenderings) {
 | 
				
			||||||
                    const el = new EditableTagRendering(
 | 
					                    elements.push(new SvelteUIElement(TagRenderingEditable, {
 | 
				
			||||||
                        otherTags,
 | 
					                        config: tagRendering,
 | 
				
			||||||
                        tagRendering,
 | 
					                        tags: otherTags,
 | 
				
			||||||
                        layer.units,
 | 
					                        selectedElement: otherFeature,
 | 
				
			||||||
                        state,
 | 
					                        state,
 | 
				
			||||||
                        {}
 | 
					                        layer
 | 
				
			||||||
                    )
 | 
					                    }))
 | 
				
			||||||
                    elements.push(el)
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (elements.length === 1) {
 | 
					                if (elements.length === 1) {
 | 
				
			||||||
                    return elements[0]
 | 
					                    return elements[0]
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return new Combine(elements).SetClass("flex flex-col")
 | 
					                return new Combine(elements).SetClass("flex flex-col")
 | 
				
			||||||
            })
 | 
					            }, [state.indexedFeatures.featuresById])
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -638,7 +630,7 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
                example:
 | 
					                example:
 | 
				
			||||||
                    "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height",
 | 
					                    "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height",
 | 
				
			||||||
                constr: (_, tagsSource, args, feature, layer) => {
 | 
					                constr: (_, tagsSource, args) => {
 | 
				
			||||||
                    const keys = args[0].split(";").map((k) => k.trim())
 | 
					                    const keys = args[0].split(";").map((k) => k.trim())
 | 
				
			||||||
                    const wikiIds: Store<string[]> = tagsSource.map((tags) => {
 | 
					                    const wikiIds: Store<string[]> = tagsSource.map((tags) => {
 | 
				
			||||||
                        const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
 | 
					                        const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								Utils.ts
									
										
									
									
									
								
							| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import colors from "./assets/colors.json"
 | 
					import colors from "./assets/colors.json"
 | 
				
			||||||
 | 
					import {HTMLElement} from "node-html-parser";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Utils {
 | 
					export class Utils {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -1365,7 +1366,25 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
 | 
				
			||||||
        d.setUTCMinutes(0)
 | 
					        d.setUTCMinutes(0)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static findParentWithScrolling(element: HTMLElement): HTMLElement {
 | 
					    public static scrollIntoView(element: HTMLBaseElement){
 | 
				
			||||||
 | 
					        console.log("Scrolling into view:", element)
 | 
				
			||||||
 | 
					        // Is the element completely in the view?
 | 
				
			||||||
 | 
					        const parentRect = Utils.findParentWithScrolling(
 | 
				
			||||||
 | 
					            element
 | 
				
			||||||
 | 
					        ).getBoundingClientRect()
 | 
				
			||||||
 | 
					        const elementRect = element.getBoundingClientRect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if the element is within the vertical bounds of the parent element
 | 
				
			||||||
 | 
					        const topIsVisible = elementRect.top >= parentRect.top
 | 
				
			||||||
 | 
					        const bottomIsVisible = elementRect.bottom <= parentRect.bottom
 | 
				
			||||||
 | 
					        const inView = topIsVisible && bottomIsVisible
 | 
				
			||||||
 | 
					        if (inView) {
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.log("Actually scrolling...")
 | 
				
			||||||
 | 
					        element.scrollIntoView({behavior: "smooth", block: "nearest"})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public static findParentWithScrolling(element: HTMLBaseElement): HTMLBaseElement {
 | 
				
			||||||
        // Check if the element itself has scrolling
 | 
					        // Check if the element itself has scrolling
 | 
				
			||||||
        if (element.scrollHeight > element.clientHeight) {
 | 
					        if (element.scrollHeight > element.clientHeight) {
 | 
				
			||||||
            return element
 | 
					            return element
 | 
				
			||||||
| 
						 | 
					@ -1377,7 +1396,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If the element has a parent, repeat the process for the parent element
 | 
					        // If the element has a parent, repeat the process for the parent element
 | 
				
			||||||
        return Utils.findParentWithScrolling(element.parentElement)
 | 
					        return Utils.findParentWithScrolling(<HTMLBaseElement> element.parentElement)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue