Allow to delete freeform keys again, partial fix of #2008

This commit is contained in:
Pieter Vander Vennet 2024-07-09 13:06:56 +02:00
parent 4df2d34f02
commit d8da61ec07
5 changed files with 56 additions and 17 deletions

View file

@ -133,7 +133,16 @@ export interface MappingConfigJson {
* question: What extra tags should be added to the object if this object is chosen? * question: What extra tags should be added to the object if this object is chosen?
* type: simple_tag * type: simple_tag
* *
* If chosen as answer, these tags will be applied onto the object, together with the tags from the `if` * If chosen as answer, these tags will be applied onto the object, together with the tags from the `if`.
* Note that if the contributor picks this mapping, saves and then changes their mind and uses a different mapping,
* the extraTags will reside.
* E.g. when picking `memorial:type=bench`, then `amenity=bench` will also be applied.
* If someone later on changes the type to `memorial:statue`, `amenity=bench` will stay onto the object
* (which is the desired behaviour, see e.g. for https://www.openstreetmap.org/node/5620038478)
* Use 'ifNot' to explicitly remove an tag if this is important
*
* If someone marks the question as 'unknown', the extra tags will not be erased
*
* Not compatible with multiAnswer. * Not compatible with multiAnswer.
* *
* This can be used e.g. to erase other keys which indicate the 'not' value: * This can be used e.g. to erase other keys which indicate the 'not' value:

View file

@ -911,6 +911,24 @@ export default class TagRenderingConfig {
return Utils.NoNull(tags) return Utils.NoNull(tags)
} }
/**
* The keys that should be erased if one has to revert to 'unknown'.
* Might give undefined
*/
public settableKeys(): string[] | undefined {
const toDelete = new Set<string>()
if(this.freeform){
toDelete.add(this.freeform.key)
}
for (const mapping of this.mappings) {
for (const usedKey of mapping.if.usedKeys()) {
toDelete.add(usedKey)
}
}
return Array.from(toDelete)
}
} }
export class TagRenderingConfigUtils { export class TagRenderingConfigUtils {

View file

@ -32,6 +32,7 @@
import { And } from "../../../Logic/Tags/And" import { And } from "../../../Logic/Tags/And"
import { get } from "svelte/store" import { get } from "svelte/store"
import Markdown from "../../Base/Markdown.svelte" import Markdown from "../../Base/Markdown.svelte"
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>>
@ -42,7 +43,7 @@
export let selectedTags: UploadableTag = undefined export let selectedTags: UploadableTag = undefined
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({}) export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
export let allowDeleteOfFreeform: boolean = false export let allowDeleteOfFreeform: boolean = true
export let clss = "interactive border-interactive" export let clss = "interactive border-interactive"
@ -141,7 +142,9 @@
feedback.setData(undefined) feedback.setData(undefined)
} }
let usedKeys: string[] = config.usedTags().flatMap((t) => t.usedKeys()) let usedKeys: string[] = Utils.Dedup(config.usedTags().flatMap((t) => t.usedKeys()))
let keysToDeleteOnUnknown = config.settableKeys()
/** /**
* The 'minimalTags' is a subset of the tags of the feature, only containing the values relevant for this object. * The 'minimalTags' is a subset of the tags of the feature, only containing the values relevant for this object.
* The main goal is to be stable and only 'ping' when an actual change is relevant * The main goal is to be stable and only 'ping' when an actual change is relevant
@ -193,10 +196,12 @@
$: { $: {
if ( if (
config.freeform?.key &&
allowDeleteOfFreeform && allowDeleteOfFreeform &&
$freeformInput === undefined && !$freeformInput &&
$freeformInputUnvalidated === "" && !$freeformInputUnvalidated &&
(config?.mappings?.length ?? 0) === 0 !checkedMappings?.some(m => m) &&
$tags[config.freeform.key] // We need to have a current value in order to delete it
) { ) {
selectedTags = new Tag(config.freeform.key, "") selectedTags = new Tag(config.freeform.key, "")
} else { } else {
@ -360,7 +365,7 @@
{/if} {/if}
{/if} {/if}
{#if config.freeform?.key && !(config?.mappings?.filter((m) => m.hideInAnswer != true)?.length > 0)} {#if config?.freeform?.key && !(config?.mappings?.filter((m) => m.hideInAnswer != true)?.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 <FreeformInput
{config} {config}
@ -480,6 +485,9 @@
<Tr t={$feedback} /> <Tr t={$feedback} />
</div> </div>
{/if} {/if}
<!--{#if keysToDeleteOnUnknown?.some(k => !! $tags[k])}
Mark as unknown (delete {keysToDeleteOnUnknown?.filter(k => !! $tags[k]).join(";")})
{/if}-->
<div <div
class="sticky bottom-0 flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap" class="sticky bottom-0 flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap"
style="z-index: 11" style="z-index: 11"
@ -487,7 +495,7 @@
<!-- TagRenderingQuestion-buttons --> <!-- TagRenderingQuestion-buttons -->
<slot name="cancel" /> <slot name="cancel" />
<slot name="save-button" {selectedTags}> <slot name="save-button" {selectedTags}>
{#if allowDeleteOfFreeform && (config?.mappings?.length ?? 0) === 0 && $freeformInput === undefined && $freeformInputUnvalidated === ""} {#if config.freeform?.key && allowDeleteOfFreeform && !checkedMappings?.some(m => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key]}
<button <button
class="primary flex" class="primary flex"
on:click|stopPropagation|preventDefault={() => onSave()} on:click|stopPropagation|preventDefault={() => onSave()}
@ -513,12 +521,14 @@
<TagHint {state} tags={selectedTags} currentProperties={$tags} /> <TagHint {state} tags={selectedTags} currentProperties={$tags} />
<span class="flex flex-wrap"> <span class="flex flex-wrap">
{#if $featureSwitchIsTesting} {#if $featureSwitchIsTesting}
<button class="small" on:click={() => console.log("Configuration is ", config)}> <div class="alert">
Testmode &nbsp; Testmode &nbsp;
</button> </div>
{/if} {/if}
{#if $featureSwitchIsTesting || $featureSwitchIsDebugging} {#if $featureSwitchIsTesting || $featureSwitchIsDebugging}
<span class="subtle">{config.id}</span> <a class="small" on:click={() => console.log("Configuration is ", config)}>
{config.id}
</a>
{/if} {/if}
</span> </span>
</span> </span>

View file

@ -28,7 +28,7 @@
export let selectedTags: UploadableTag = undefined export let selectedTags: UploadableTag = undefined
export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({}) export let extraTags: UIEventSource<Record<string, string>> = new UIEventSource({})
export let allowDeleteOfFreeform: boolean = false export let allowDeleteOfFreeform: boolean = true
let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement) let dynamicConfig = TagRenderingConfigUtils.withNameSuggestionIndex(config, tags, selectedElement)
</script> </script>

View file

@ -382,13 +382,15 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
/** /**
* Creates a new array with all elements from 'arr' in such a way that every element will be kept only once * Creates a new array with all elements from 'arr' in such a way that every element will be kept only once
* Elements are returned in the same order as they appear in the lists * Elements are returned in the same order as they appear in the lists.
* @param arr * Null/Undefined is returned as is. If an emtpy array is given, a new empty array will be returned
* @constructor
*/ */
public static Dedup(arr: NonNullable<string[]>): NonNullable<string[]>
public static Dedup(arr: undefined):undefined
public static Dedup(arr: string[] | undefined): string[] | undefined
public static Dedup(arr: string[]): string[] { public static Dedup(arr: string[]): string[] {
if (arr === undefined) { if (arr === undefined || arr === null) {
return undefined return arr
} }
const newArr = [] const newArr = []
for (const string of arr) { for (const string of arr) {