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:
parent
b0052d3a36
commit
1123a72c5e
25 changed files with 390 additions and 531 deletions
|
@ -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})
|
||||
])
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue