forked from MapComplete/MapComplete
		
	Feature: allow to remove an answer (in most cases), fix #2008
This commit is contained in:
		
							parent
							
								
									7565f13e39
								
							
						
					
					
						commit
						55c89085a3
					
				
					 8 changed files with 181 additions and 86 deletions
				
			
		|  | @ -12,11 +12,15 @@ | |||
| </script> | ||||
| 
 | ||||
| {#if !onlyLink} | ||||
| <Popup {shown} {bodyPadding} {fullscreen}/> | ||||
|   <Popup {shown} {bodyPadding} {fullscreen}> | ||||
|     <slot name="header" slot="header" /> | ||||
|     <slot /> | ||||
|     <slot name="footer" slot="footer" /> | ||||
|   </Popup> | ||||
| {:else} | ||||
|   <button class="as-link sidebar-button" on:click={() => shown.setData(true)}> | ||||
|     <slot name="link"> | ||||
|     <slot name="header" /> | ||||
|       <slot name="header" /> | ||||
|     </slot> | ||||
|   </button> | ||||
| {/if} | ||||
|  | @ -27,9 +31,9 @@ | |||
|         align-items: center; | ||||
|     } | ||||
| 
 | ||||
|   :global(.page-header svg) { | ||||
|       width: 2rem; | ||||
|       height: 2rem; | ||||
|       margin-right: 0.75rem; | ||||
|   } | ||||
|     :global(.page-header svg) { | ||||
|         width: 2rem; | ||||
|         height: 2rem; | ||||
|         margin-right: 0.75rem; | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ | |||
| 
 | ||||
| <Modal open={_shown} on:close={() => shown.set(false)} outsideclose | ||||
|        size="xl" | ||||
|        dismissable={false} | ||||
|        dismissable={false}P | ||||
|        {defaultClass} {bodyClass} {dialogClass} {headerClass} | ||||
|        color="none"> | ||||
|   <h1 slot="header" class="page-header w-full"> | ||||
|  |  | |||
|  | @ -33,6 +33,9 @@ | |||
|   import Markdown from "../../Base/Markdown.svelte" | ||||
|   import { Utils } from "../../../Utils" | ||||
|   import type { UploadableTag } from "../../../Logic/Tags/TagTypes" | ||||
|   import { Modal } from "flowbite-svelte" | ||||
|   import Popup from "../../Base/Popup.svelte" | ||||
|   import If from "../../Base/If.svelte" | ||||
| 
 | ||||
|   export let config: TagRenderingConfig | ||||
|   export let tags: UIEventSource<Record<string, string>> | ||||
|  | @ -48,6 +51,8 @@ | |||
|   let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined) | ||||
| 
 | ||||
|   let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key)) | ||||
|   let isKnown = tags.mapD(tags => config.GetRenderValue(tags) !== undefined) | ||||
|   let matchesEmpty = config.GetRenderValue({}) !== undefined | ||||
| 
 | ||||
|   // Will be bound if a freeform is available | ||||
|   let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]) | ||||
|  | @ -59,6 +64,12 @@ | |||
|    */ | ||||
|   let checkedMappings: boolean[] | ||||
| 
 | ||||
|   /** | ||||
|    * IF set: we can remove the current answer by deleting all those keys | ||||
|    */ | ||||
|   let settableKeys = config.removeToSetUnknown() | ||||
|   let unknownModal = new UIEventSource(false) | ||||
| 
 | ||||
|   let searchTerm: UIEventSource<string> = new UIEventSource("") | ||||
| 
 | ||||
|   let dispatch = createEventDispatcher<{ | ||||
|  | @ -80,7 +91,7 @@ | |||
|       return !m.hideInAnswer.matchesProperties(tgs) | ||||
|     }) | ||||
|     selectedMapping = mappings?.findIndex( | ||||
|       (mapping) => mapping.if.matchesProperties(tgs) || mapping.alsoShowIf?.matchesProperties(tgs) | ||||
|       (mapping) => mapping.if.matchesProperties(tgs) || mapping.alsoShowIf?.matchesProperties(tgs), | ||||
|     ) | ||||
|     if (selectedMapping < 0) { | ||||
|       selectedMapping = undefined | ||||
|  | @ -142,7 +153,6 @@ | |||
| 
 | ||||
|   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 main goal is to be stable and only 'ping' when an actual change is relevant | ||||
|  | @ -189,7 +199,7 @@ | |||
|       if (freeformValue?.length > 0) { | ||||
|         selectedMapping = config.mappings.length | ||||
|       } | ||||
|     }) | ||||
|     }), | ||||
|   ) | ||||
| 
 | ||||
|   $: { | ||||
|  | @ -207,7 +217,7 @@ | |||
|           $freeformInput, | ||||
|           selectedMapping, | ||||
|           checkedMappings, | ||||
|           tags.data | ||||
|           tags.data, | ||||
|         ) | ||||
|         if (featureSwitchIsDebugging?.data) { | ||||
|           console.log( | ||||
|  | @ -219,7 +229,7 @@ | |||
|               currentTags: tags.data, | ||||
|             }, | ||||
|             " --> ", | ||||
|             selectedTags | ||||
|             selectedTags, | ||||
|           ) | ||||
|         } | ||||
|       } catch (e) { | ||||
|  | @ -241,7 +251,7 @@ | |||
|         selectedTags = new And([...selectedTags.and, ...extraTagsArray]) | ||||
|       } else { | ||||
|         console.error( | ||||
|           "selectedTags is not of type Tag or And, it is a " + JSON.stringify(selectedTags) | ||||
|           "selectedTags is not of type Tag or And, it is a " + JSON.stringify(selectedTags), | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
|  | @ -310,9 +320,24 @@ | |||
|     onDestroy( | ||||
|       state.osmConnection?.userDetails?.addCallbackAndRun((ud) => { | ||||
|         numberOfCs = ud.csCount | ||||
|       }) | ||||
|       }), | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   function clearAnswer() { | ||||
|     const tagsToSet = settableKeys.map(k => new Tag(k, "")) | ||||
|     const change = new ChangeTagAction(tags.data.id, new And(tagsToSet), tags.data, { | ||||
|       theme: tags.data["_orig_theme"] ?? state.layout.id, | ||||
|       changeType: "answer", | ||||
|     }) | ||||
|     freeformInput.set(undefined) | ||||
|     selectedMapping = undefined | ||||
|     selectedTags = undefined | ||||
|     change | ||||
|       .CreateChangeDescriptions() | ||||
|       .then((changes) => state.changes.applyChanges(changes)) | ||||
|       .catch(console.error) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| {#if question !== undefined} | ||||
|  | @ -491,36 +516,74 @@ | |||
|               <Tr t={$feedback} /> | ||||
|             </div> | ||||
|           {/if} | ||||
|           <!--{#if keysToDeleteOnUnknown?.some(k => !! $tags[k])} | ||||
|             Mark as unknown (delete {keysToDeleteOnUnknown?.filter(k => !! $tags[k]).join(";")}) | ||||
|             {/if}--> | ||||
| 
 | ||||
| 
 | ||||
|           <Popup shown={unknownModal}> | ||||
|             <h2 slot="header"> | ||||
|               <Tr t={Translations.t.unknown.title} /> | ||||
|             </h2> | ||||
|             <Tr t={Translations.t.unknown.explanation} /> | ||||
|             <If condition={state.userRelatedState.showTags.map(v => v === "yes" || v === "full" || v === "always")}> | ||||
|               <div class="subtle"> | ||||
|                 <Tr t={Translations.t.unknown.removedKeys}/> | ||||
|                 {#each settableKeys as key} | ||||
|                   <code> | ||||
|                     <del> | ||||
|                       {key} | ||||
|                     </del> | ||||
|                   </code> | ||||
|                 {/each} | ||||
|               </div> | ||||
|             </If> | ||||
|             <div class="flex justify-end w-full" slot="footer"> | ||||
|               <button on:click={() => unknownModal.set(false)}> | ||||
|                 <Tr t={Translations.t.unknown.keep} /> | ||||
|               </button> | ||||
|               <button class="primary" on:click={() => {unknownModal.set(false); clearAnswer()}}> | ||||
|                 <Tr t={Translations.t.unknown.clear} /> | ||||
|               </button> | ||||
|             </div> | ||||
|           </Popup> | ||||
| 
 | ||||
|           <div | ||||
|             class="sticky bottom-0 flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap" | ||||
|             class="sticky bottom-0 flex justify-between flex-wrap" | ||||
|             style="z-index: 11" | ||||
|           > | ||||
|             <!-- TagRenderingQuestion-buttons --> | ||||
|             <slot name="cancel" /> | ||||
|             <slot name="save-button" {selectedTags}> | ||||
|               {#if config.freeform?.key && !checkedMappings?.some((m) => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key]} | ||||
|                 <button | ||||
|                   class="primary flex" | ||||
|                   on:click|stopPropagation|preventDefault={() => onSave()} | ||||
|                 > | ||||
|                   <TrashIcon class="h-6 w-6 text-red-500" /> | ||||
|                   <Tr t={Translations.t.general.eraseValue} /> | ||||
|                 </button> | ||||
|               {:else} | ||||
|                 <button | ||||
|                   on:click={() => onSave()} | ||||
|                   class={twJoin( | ||||
| 
 | ||||
|             {#if settableKeys && $isKnown && !matchesEmpty } | ||||
|               <button class="as-link small text-sm" on:click={() => unknownModal.set(true)}> | ||||
|                 <Tr t={Translations.t.unknown.markUnknown} /> | ||||
|               </button> | ||||
|             {/if} | ||||
| 
 | ||||
| 
 | ||||
|             <div class="flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap self-end flex-grow"> | ||||
| 
 | ||||
|               <!-- TagRenderingQuestion-buttons --> | ||||
|               <slot name="cancel" /> | ||||
|               <slot name="save-button" {selectedTags}> | ||||
|                 {#if config.freeform?.key && !checkedMappings?.some((m) => m) && !$freeformInput && !$freeformInputUnvalidated && $tags[config.freeform.key]} | ||||
|                   <button | ||||
|                     class="primary flex" | ||||
|                     on:click|stopPropagation|preventDefault={() => onSave()} | ||||
|                   > | ||||
|                     <TrashIcon class="h-6 w-6 text-red-500" /> | ||||
|                     <Tr t={Translations.t.general.eraseValue} /> | ||||
|                   </button> | ||||
|                 {:else} | ||||
|                   <button | ||||
|                     on:click={() => onSave()} | ||||
|                     class={twJoin( | ||||
|                     selectedTags === undefined ? "disabled" : "button-shadow", | ||||
|                     "primary" | ||||
|                   )} | ||||
|                 > | ||||
|                   <Tr t={Translations.t.general.save} /> | ||||
|                 </button> | ||||
|               {/if} | ||||
|             </slot> | ||||
|                   > | ||||
|                     <Tr t={Translations.t.general.save} /> | ||||
|                   </button> | ||||
|                 {/if} | ||||
|               </slot> | ||||
|             </div> | ||||
| 
 | ||||
|           </div> | ||||
|           {#if UserRelatedState.SHOW_TAGS_VALUES.indexOf($showTags) >= 0 || ($showTags === "" && numberOfCs >= Constants.userJourney.tagsVisibleAt) || $featureSwitchIsTesting || $featureSwitchIsDebugging} | ||||
|             <span class="flex flex-wrap justify-between"> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue