Feat: allow to disable questions (and to enable them again), fix #256

This commit is contained in:
Pieter Vander Vennet 2024-10-08 22:37:11 +02:00
parent f8ef32f123
commit 93ebdd8e16
8 changed files with 238 additions and 76 deletions

View file

@ -738,6 +738,14 @@
} }
] ]
}, },
{
"id": "disabled-questions",
"render": {
"special": {
"type": "disabled_questions"
}
}
},
{ {
"id": "title-privacy-legal", "id": "title-privacy-legal",
"render": { "render": {

View file

@ -545,4 +545,14 @@ export default class UserRelatedState {
return amendedPrefs return amendedPrefs
} }
/**
* The disabled questions for this theme and layer
*/
public getThemeDisabled(themeId: string, layerId: string): UIEventSource<string[]> {
const flatSource = this.osmConnection.getPreference("disabled-questions-" + themeId + "-" + layerId, "[]")
return UIEventSource.asObject<string[]>(flatSource, [])
}
} }

View file

@ -9,17 +9,17 @@
export let open = new UIEventSource(false) export let open = new UIEventSource(false)
export let dotsSize = `w-6 h-6` export let dotsSize = `w-6 h-6`
export let dotsPosition = `top-0 right-0` export let dotsPosition = `top-0 right-0`
export let hideBackground= false export let hideBackground: boolean = false
let menuPosition = `` let menuPosition = ``
if(dotsPosition.indexOf("left-0") >= 0){ if (dotsPosition.indexOf("left-0") >= 0) {
menuPosition = "left-0" menuPosition = "left-0"
}else{ } else {
menuPosition = `right-0` menuPosition = `right-0`
} }
if(dotsPosition.indexOf("top-0") > 0){ if (dotsPosition.indexOf("top-0") > 0) {
menuPosition += " bottom-0" menuPosition += " bottom-0"
}else{ } else {
menuPosition += ` top-0` menuPosition += ` top-0`
} }
@ -49,7 +49,7 @@
} }
:global(.dots-menu > path) { :global(.dots-menu > path) {
fill: var(--interactive-background); fill: var(--button-background-hover);
transition: fill 350ms linear; transition: fill 350ms linear;
cursor: pointer; cursor: pointer;
@ -74,7 +74,7 @@
} }
.transition-background { .transition-background {
transition: background-color 150ms linear; transition: background-color 150ms linear;
} }
.transition-background.collapsed { .transition-background.collapsed {

View file

@ -0,0 +1,23 @@
<script lang="ts">
import DisabledQuestionsLayer from "./DisabledQuestionsLayer.svelte"
import { Stores } from "../../Logic/UIEventSource"
/**
* Shows _all_ disabled questions
*/
export let state
let layers = state.layout.layers.filter(l => l.isNormal())
let allDisabled = Stores.concat<string>(layers.map(l => state.userRelatedState.getThemeDisabled(state.layout.id, l.id))).map(l => [].concat(...l))
</script>
<h3>Disabled questions</h3>
{#if $allDisabled.length === 0}
To disable a question, click the three dots in the upper-right corner
{:else}
To enable a question again, click it
{#each layers as layer (layer.id)}
<DisabledQuestionsLayer {state} {layer} />
{/each}
{/if}

View file

@ -0,0 +1,45 @@
<script lang="ts">/**
* Gives an overview of questions which are disabled for the given theme
*/
import UserRelatedState from "../../Logic/State/UserRelatedState"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import ThemeViewState from "../../Models/ThemeViewState"
import Tr from "../Base/Tr.svelte"
import { Translation } from "../i18n/Translation"
import { XMarkIcon } from "@babeard/svelte-heroicons/mini"
import ToSvelte from "../Base/ToSvelte.svelte"
export let layer: LayerConfig
export let state: ThemeViewState
let disabledQuestions = state.userRelatedState.getThemeDisabled(state.layout.id, layer.id)
function getQuestion(id: string): Translation {
return layer.tagRenderings.find(q => q.id === id).question.Subs({})
}
function enable(idToEnable: string) {
const newList = disabledQuestions.data.filter(id => id !== idToEnable)
disabledQuestions.set(newList)
}
</script>
{#if $disabledQuestions.length > 0}
<div class="low-interaction p-2">
<h4 class="flex my-2">
<div class="no-image-background block h-6 w-6">
<ToSvelte construct={() => layer.defaultIcon()} />
</div>
<Tr t={layer.name} />
</h4>
<div class="flex">
{#each $disabledQuestions as id}
<button class="badge button-unstyled" on:click={() => enable(id)}>
<Tr cls="ml-2" t={getQuestion(id)} />
<XMarkIcon class="w-4 h-4 mr-2" />
</button>
{/each}
</div>
</div>
{/if}

View file

@ -43,11 +43,20 @@
} }
return true return true
} }
const baseQuestions = (layer?.tagRenderings ?? [])?.filter( const baseQuestions = (layer?.tagRenderings ?? [])?.filter(
(tr) => allowed(tr.labels) && tr.question !== undefined (tr) => allowed(tr.labels) && tr.question !== undefined,
) )
/**
* Ids of skipped questions
*/
let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>()) let skippedQuestions = new UIEventSource<Set<string>>(new Set<string>())
let layerDisabledForTheme = state.userRelatedState.getThemeDisabled(state.layout.id, layer.id)
layerDisabledForTheme.addCallbackAndRunD(disabled => {
skippedQuestions.set(new Set(disabled.concat(Array.from(skippedQuestions.data))))
})
let questionboxElem: HTMLDivElement let questionboxElem: HTMLDivElement
let questionsToAsk = tags.map( let questionsToAsk = tags.map(
(tags) => { (tags) => {
@ -69,10 +78,10 @@
} }
return questionsToAsk return questionsToAsk
}, },
[skippedQuestions] [skippedQuestions],
) )
let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>( let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>(
undefined undefined,
) )
let allQuestionsToAsk: UIEventSource<TagRenderingConfig[]> = new UIEventSource< let allQuestionsToAsk: UIEventSource<TagRenderingConfig[]> = new UIEventSource<
TagRenderingConfig[] TagRenderingConfig[]
@ -95,6 +104,8 @@
let skipped: number = 0 let skipped: number = 0
let loginEnabled = state.featureSwitches.featureSwitchEnableLogin let loginEnabled = state.featureSwitches.featureSwitchEnableLogin
let debug = state.featureSwitches.featureSwitchIsDebugging
function skip(question: { id: string }, didAnswer: boolean = false) { function skip(question: { id: string }, didAnswer: boolean = false) {
skippedQuestions.data.add(question.id) // Must use ID, the config object might be a copy of the original skippedQuestions.data.add(question.id) // Must use ID, the config object might be a copy of the original
@ -117,43 +128,84 @@
class="marker-questionbox-root" class="marker-questionbox-root"
class:hidden={$questionsToAsk.length === 0 && skipped === 0 && answered === 0} class:hidden={$questionsToAsk.length === 0 && skipped === 0 && answered === 0}
> >
{#if $showAllQuestionsAtOnce}
<div class="flex flex-col gap-y-1">
{#each $allQuestionsToAsk as question (question.id)}
<TagRenderingQuestionDynamic
config={question}
{tags}
{selectedElement}
{state}
{layer}
/>
{/each}
</div>
{:else if $firstQuestion !== undefined}
<TagRenderingQuestionDynamic
config={$firstQuestion}
{layer}
{selectedElement}
{state}
{tags}
on:saved={() => {
skip($firstQuestion, true)
}}
>
<button
class="secondary"
on:click={() => {
skip($firstQuestion)
}}
slot="cancel"
>
<Tr t={Translations.t.general.skip} />
</button>
</TagRenderingQuestionDynamic>
{/if}
{#if $allQuestionsToAsk.length === 0} {#if $allQuestionsToAsk.length === 0}
<div class="thanks">
<Tr t={Translations.t.general.questionBox.done} />
</div>
{/if}
<div class="mt-4 mb-8">
{#if skipped + answered > 0} {#if skipped + answered > 0}
<div class="thanks"> <div class="flex justify-center">
<Tr t={Translations.t.general.questionBox.done} /> {#if answered === 0}
</div> {#if skipped === 1}
{#if answered === 0} <Tr t={Translations.t.general.questionBox.skippedOne} />
{#if skipped === 1} {:else}
<Tr t={Translations.t.general.questionBox.skippedOne} /> <Tr t={Translations.t.general.questionBox.skippedMultiple.Subs({ skipped })} />
{:else} {/if}
<Tr t={Translations.t.general.questionBox.skippedMultiple.Subs({ skipped })} /> {:else if answered === 1}
{/if} {#if skipped === 0}
{:else if answered === 1} <Tr t={Translations.t.general.questionBox.answeredOne} />
{#if skipped === 0} {:else if skipped === 1}
<Tr t={Translations.t.general.questionBox.answeredOne} /> <Tr t={Translations.t.general.questionBox.answeredOneSkippedOne} />
{:else}
<Tr
t={Translations.t.general.questionBox.answeredOneSkippedMultiple.Subs({ skipped })}
/>
{/if}
{:else if skipped === 0}
<Tr t={Translations.t.general.questionBox.answeredMultiple.Subs({ answered })} />
{:else if skipped === 1} {:else if skipped === 1}
<Tr t={Translations.t.general.questionBox.answeredOneSkippedOne} /> <Tr
t={Translations.t.general.questionBox.answeredMultipleSkippedOne.Subs({ answered })}
/>
{:else} {:else}
<Tr <Tr
t={Translations.t.general.questionBox.answeredOneSkippedMultiple.Subs({ skipped })} t={Translations.t.general.questionBox.answeredMultipleSkippedMultiple.Subs({
/>
{/if}
{:else if skipped === 0}
<Tr t={Translations.t.general.questionBox.answeredMultiple.Subs({ answered })} />
{:else if skipped === 1}
<Tr
t={Translations.t.general.questionBox.answeredMultipleSkippedOne.Subs({ answered })}
/>
{:else}
<Tr
t={Translations.t.general.questionBox.answeredMultipleSkippedMultiple.Subs({
answered, answered,
skipped, skipped,
})} })}
/> />
{/if} {/if}
</div>
{#if skipped > 0} {#if skipped + $skippedQuestions.size > 0}
<button <button
class="w-full" class="w-full"
on:click={() => { on:click={() => {
@ -163,45 +215,25 @@
> >
<Tr t={Translations.t.general.questionBox.reactivate} /> <Tr t={Translations.t.general.questionBox.reactivate} />
</button> </button>
{/if} {/if}
{/if} {/if}
{:else}
<div> {#if $skippedQuestions.size - skipped > 0}
{#if $showAllQuestionsAtOnce} <button
<div class="flex flex-col gap-y-1"> class="w-full"
{#each $allQuestionsToAsk as question (question.id)} on:click={() => {
<TagRenderingQuestionDynamic skippedQuestions.setData(new Set())
config={question} skipped = 0
{tags}
{selectedElement}
{state}
{layer}
/>
{/each}
</div>
{:else if $firstQuestion !== undefined}
<TagRenderingQuestionDynamic
config={$firstQuestion}
{layer}
{selectedElement}
{state}
{tags}
on:saved={() => {
skip($firstQuestion, true)
}} }}
> >
<button Show the disabled questions for this object
class="secondary" </button>
on:click={() => {
skip($firstQuestion) {/if}
}} {#if $debug}
slot="cancel" Skipped questions are {Array.from($skippedQuestions).join(", ")}
> {/if}
<Tr t={Translations.t.general.skip} /> </div>
</button>
</TagRenderingQuestionDynamic>
{/if}
</div>
{/if}
</div> </div>
{/if} {/if}

View file

@ -36,6 +36,8 @@
import { Modal } from "flowbite-svelte" import { Modal } from "flowbite-svelte"
import Popup from "../../Base/Popup.svelte" import Popup from "../../Base/Popup.svelte"
import If from "../../Base/If.svelte" import If from "../../Base/If.svelte"
import DotMenu from "../../Base/DotMenu.svelte"
import SidebarUnit from "../../Base/SidebarUnit.svelte"
export let config: TagRenderingConfig export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>> export let tags: UIEventSource<Record<string, string>>
@ -338,10 +340,41 @@
.then((changes) => state.changes.applyChanges(changes)) .then((changes) => state.changes.applyChanges(changes))
.catch(console.error) .catch(console.error)
} }
let disabledInTheme = state.userRelatedState.getThemeDisabled(state.layout.id, layer?.id)
let menuIsOpened = new UIEventSource(false)
function disableQuestion() {
const newList = Utils.Dedup([config.id, ...disabledInTheme.data])
disabledInTheme.set(newList)
menuIsOpened.set(false)
}
function enableQuestion() {
const newList = disabledInTheme.data?.filter(id => id !== config.id)
disabledInTheme.set(newList)
menuIsOpened.set(false)
}
</script> </script>
{#if question !== undefined} {#if question !== undefined}
<div class={clss}> <div class={clss}>
{#if layer.isNormal()}
<DotMenu hideBackground={true} open={menuIsOpened}>
<SidebarUnit>
{#if $disabledInTheme.indexOf(config.id) >= 0}
<button on:click={() => enableQuestion()}>
Ask this question for all features
</button>
{:else}
<button on:click={() => disableQuestion()}>
Don't ask this question again
</button>
{/if}
</SidebarUnit>
</DotMenu>
{/if}
<form <form
class="relative flex flex-col overflow-y-auto px-4" class="relative flex flex-col overflow-y-auto px-4"
style="max-height: 75vh" style="max-height: 75vh"
@ -525,7 +558,7 @@
<Tr t={Translations.t.unknown.explanation} /> <Tr t={Translations.t.unknown.explanation} />
<If condition={state.userRelatedState.showTags.map(v => v === "yes" || v === "full" || v === "always")}> <If condition={state.userRelatedState.showTags.map(v => v === "yes" || v === "full" || v === "always")}>
<div class="subtle"> <div class="subtle">
<Tr t={Translations.t.unknown.removedKeys}/> <Tr t={Translations.t.unknown.removedKeys} />
{#each $settableKeys as key} {#each $settableKeys as key}
<code> <code>
<del> <del>

View file

@ -97,6 +97,7 @@ import ClearCaches from "./Popup/ClearCaches.svelte"
import GroupedView from "./Popup/GroupedView.svelte" import GroupedView from "./Popup/GroupedView.svelte"
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte" import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte"
import DisabledQuestions from "./Popup/DisabledQuestions.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
@ -2113,6 +2114,16 @@ export default class SpecialVisualizations {
}) })
}, },
}, },
{
funcName: "disabled_questions",
docs: "Shows which questions are disabled for every layer. Used in 'settings'",
needsUrls: [],
args: [],
constr(state) {
return new SvelteUIElement(DisabledQuestions, { state })
},
},
] ]
specialVisualizations.push(new AutoApplyButton(specialVisualizations)) specialVisualizations.push(new AutoApplyButton(specialVisualizations))