Refactoring: fix most of the custom input elements, support right click/long tap/double click to add a new element

This commit is contained in:
Pieter Vander Vennet 2023-04-16 03:42:26 +02:00
parent b0052d3a36
commit 1123a72c5e
25 changed files with 390 additions and 531 deletions

View file

@ -24,6 +24,8 @@ import TagRenderingQuestion from "./TagRenderingQuestion"
import { OsmId, OsmTags } from "../../Models/OsmFeature"
import { LoginToggle } from "./LoginButton"
import { SpecialVisualizationState } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement";
import TagHint from "./TagHint.svelte";
export default class DeleteWizard extends Toggle {
/**
@ -225,11 +227,7 @@ export default class DeleteWizard extends Toggle {
// This is a retagging, not a deletion of any kind
return new Combine([
t.explanations.retagNoOtherThemes,
TagRenderingQuestion.CreateTagExplanation(
new UIEventSource<TagsFilter>(retag),
currentTags,
state
).SetClass("subtle"),
new SvelteUIElement(TagHint, {osmConnection: state.osmConnection, tags: retag})
])
}

View file

@ -11,13 +11,13 @@
* A 'TagHint' will show the given tags in a human readable form.
* Depending on the options, it'll link through to the wiki or might be completely hidden
*/
export let tags: TagsFilter;
export let osmConnection: OsmConnection;
/**
* If given, this function will be called to embed the given tags hint into this translation
*/
export let embedIn: (() => Translation) | undefined = undefined;
const userDetails = osmConnection.userDetails;
export let tags: TagsFilter;
let linkToWiki = false;
onDestroy(osmConnection.userDetails.addCallbackAndRunD(userdetails => {
linkToWiki = userdetails.csCount > Constants.userJourney.tagsVisibleAndWikiLinked;

View file

@ -5,28 +5,36 @@
import Tr from "../../Base/Tr.svelte";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import Inline from "./Inline.svelte";
import { createEventDispatcher } from "svelte";
import { createEventDispatcher, onDestroy } from "svelte";
import InputHelper from "../../InputElement/InputHelper.svelte";
import type { Feature } from "geojson";
export let value: UIEventSource<string>;
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let feature: Feature = undefined;
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
let dispatch = createEventDispatcher<{ "selected" }>();
onDestroy(value.addCallbackD(() => {dispatch("selected")}))
</script>
{#if config.freeform.inline}
<Inline key={config.freeform.key} {tags} template={config.render}>
<div class="inline-flex flex-col">
{#if config.freeform.inline}
<Inline key={config.freeform.key} {tags} template={config.render}>
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
type={config.freeform.type} {value}></ValidatedInput>
</Inline>
{:else}
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
type={config.freeform.type} {value}></ValidatedInput>
</Inline>
{:else}
<ValidatedInput {feedback} on:selected={() => dispatch("selected")}
type={config.freeform.type} {value}></ValidatedInput>
{/if}
{/if}
<InputHelper args={config.freeform.helperArgs} {config} {feature} type={config.freeform.type} {value}></InputHelper>
</div>
{#if $feedback !== undefined}
<div class="alert">

View file

@ -41,13 +41,11 @@
return true;
}
let baseQuestions = []
$: {
baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined);
}
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>());
let questionsToAsk = tags.map(tags => {
const baseQuestions = (layer.tagRenderings ?? [])?.filter(tr => allowed(tr.labels) && tr.question !== undefined);
console.log("Determining questions for", baseQuestions)
const questionsToAsk: TagRenderingConfig[] = [];
for (const baseQuestion of baseQuestions) {
if (skippedQuestions.data.has(baseQuestion.id) > 0) {
@ -64,6 +62,7 @@
return questionsToAsk;
}, [skippedQuestions]);
let _questionsToAsk: TagRenderingConfig[];
let _firstQuestion: TagRenderingConfig;
onDestroy(questionsToAsk.subscribe(qta => {

View file

@ -17,25 +17,27 @@
export let state: SpecialVisualizationState;
export let selectedElement: Feature;
export let config: TagRenderingConfig;
if(config === undefined){
throw "Config is undefined in tagRenderingAnswer"
if (config === undefined) {
throw "Config is undefined in tagRenderingAnswer";
}
export let layer: LayerConfig
export let layer: LayerConfig;
let trs: { then: Translation; icon?: string; iconClass?: string }[];
$: trs = Utils.NoNull(config?.GetRenderValues(_tags));
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))}
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
{/if}
{#if trs.length > 1}
<ul>
{#each trs as mapping}
<li>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
</li>
{/each}
</ul>
{/if}
<div class="flex flex-col w-full">
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
{/if}
{#if trs.length > 1}
<ul>
{#each trs as mapping}
<li>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
</li>
{/each}
</ul>
{/if}
</div>
{/if}

View file

@ -11,7 +11,7 @@
import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher } from "svelte";
import { createEventDispatcher, onDestroy } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
import SpecialTranslation from "./SpecialTranslation.svelte";
@ -25,6 +25,12 @@
// Will be bound if a freeform is available
let freeformInput = new UIEventSource<string>(undefined);
onDestroy(tags.addCallbackAndRunD(tags => {
// initialize with the previous value
if (config.freeform?.key) {
freeformInput.setData(tags[config.freeform.key]);
}
}));
let selectedMapping: number = undefined;
let checkedMappings: boolean[];
$: {
@ -126,7 +132,7 @@
{#if config.freeform?.key && !(mappings?.length > 0)}
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
<FreeformInput {config} {tags} value={freeformInput} />
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput} />
{:else if mappings !== undefined && !config.multiAnswer}
<!-- Simple radiobuttons as mapping -->
<div class="flex flex-col">
@ -143,7 +149,7 @@
<label>
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id}
value={config.mappings.length}>
<FreeformInput {config} {tags} value={freeformInput}
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
on:selected={() => selectedMapping = config.mappings.length } />
</label>
{/if}
@ -162,7 +168,7 @@
<label>
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings.length}
bind:checked={checkedMappings[config.mappings.length]}>
<FreeformInput {config} {tags} value={freeformInput}
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}
on:selected={() => checkedMappings[config.mappings.length] = true} />
</label>
{/if}
@ -180,7 +186,7 @@
{:else }
<div class="w-6 h-6">
<!-- Invalid value; show an inactive button or something like that-->
<ExclamationIcon></ExclamationIcon>
<ExclamationIcon/>
</div>
{/if}
</div>

View file

@ -25,10 +25,8 @@ import TagRenderingConfig, { Mapping } from "../../Models/ThemeConfig/TagRenderi
import { Unit } from "../../Models/Unit"
import VariableInputElement from "../Input/VariableInputElement"
import Toggle from "../Input/Toggle"
import Img from "../Base/Img"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import Title from "../Base/Title"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { GeoOperations } from "../../Logic/GeoOperations"
import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
import { OsmTags } from "../../Models/OsmFeature"
@ -47,7 +45,6 @@ export default class TagRenderingQuestion extends Combine {
afterSave?: () => void
cancelButton?: BaseUIElement
saveButtonConstr?: (src: Store<TagsFilter>) => BaseUIElement
bottomText?: (src: Store<UploadableTag>) => BaseUIElement
}
) {
const applicableMappingsSrc = Stores.ListStabilized(
@ -134,26 +131,15 @@ export default class TagRenderingQuestion extends Combine {
const saveButton = new Combine([options.saveButtonConstr(inputElement.GetValue())])
let bottomTags: BaseUIElement
if (options.bottomText !== undefined) {
bottomTags = options.bottomText(inputElement.GetValue())
} else {
bottomTags = TagRenderingQuestion.CreateTagExplanation(
inputElement.GetValue(),
tags,
state
)
}
super([
question,
questionHint,
inputElement,
new VariableUiElement(
feedback.map(
(t) =>
t
?.SetStyle("padding-left: 0.75rem; padding-right: 0.75rem")
?.SetClass("alert flex") ?? bottomTags
feedback.map((t) =>
t
?.SetStyle("padding-left: 0.75rem; padding-right: 0.75rem")
?.SetClass("alert flex")
)
),
new Combine([options.cancelButton, saveButton]).SetClass(
@ -634,14 +620,7 @@ export default class TagRenderingQuestion extends Combine {
tagsSource: UIEventSource<any>,
state: FeaturePipelineState
): BaseUIElement {
const text = new SubstitutedTranslation(mapping.then, tagsSource, state)
if (mapping.icon === undefined) {
return text
}
return new Combine([
new Img(mapping.icon).SetClass("mr-1 mapping-icon-" + (mapping.iconClass ?? "small")),
text,
]).SetClass("flex items-center")
return undefined
}
private static GenerateFreeform(
@ -703,9 +682,6 @@ export default class TagRenderingQuestion extends Combine {
feedback,
})
// Init with correct value
input?.GetValue().setData(tagsData[freeform.key] ?? freeform.default)
// Add a length check
input?.GetValue().addCallbackD((v: string | undefined) => {
if (v?.length >= 255) {
@ -734,32 +710,4 @@ export default class TagRenderingQuestion extends Combine {
return inputTagsFilter
}
public static CreateTagExplanation(
selectedValue: Store<TagsFilter>,
tags: Store<object>,
state?: { osmConnection?: OsmConnection }
) {
return new VariableUiElement(
selectedValue.map(
(tagsFilter: TagsFilter) => {
const csCount =
state?.osmConnection?.userDetails?.data?.csCount ??
Constants.userJourney.tagsVisibleAndWikiLinked + 1
if (csCount < Constants.userJourney.tagsVisibleAt) {
return ""
}
if (tagsFilter === undefined) {
return Translations.t.general.noTagsSelected.SetClass("subtle")
}
if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) {
const tagsStr = tagsFilter.asHumanString(false, true, tags.data)
return new FixedUiElement(tagsStr).SetClass("subtle")
}
return tagsFilter.asHumanString(true, true, tags.data)
},
[state?.osmConnection?.userDetails]
)
).SetClass("block break-all")
}
}