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",
"render": {

View file

@ -545,4 +545,14 @@ export default class UserRelatedState {
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 dotsSize = `w-6 h-6`
export let dotsPosition = `top-0 right-0`
export let hideBackground= false
export let hideBackground: boolean = false
let menuPosition = ``
if(dotsPosition.indexOf("left-0") >= 0){
if (dotsPosition.indexOf("left-0") >= 0) {
menuPosition = "left-0"
}else{
} else {
menuPosition = `right-0`
}
if(dotsPosition.indexOf("top-0") > 0){
if (dotsPosition.indexOf("top-0") > 0) {
menuPosition += " bottom-0"
}else{
} else {
menuPosition += ` top-0`
}
@ -49,7 +49,7 @@
}
:global(.dots-menu > path) {
fill: var(--interactive-background);
fill: var(--button-background-hover);
transition: fill 350ms linear;
cursor: pointer;
@ -74,7 +74,7 @@
}
.transition-background {
transition: background-color 150ms linear;
transition: background-color 150ms linear;
}
.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
}
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 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 questionsToAsk = tags.map(
(tags) => {
@ -69,10 +78,10 @@
}
return questionsToAsk
},
[skippedQuestions]
[skippedQuestions],
)
let firstQuestion: UIEventSource<TagRenderingConfig> = new UIEventSource<TagRenderingConfig>(
undefined
undefined,
)
let allQuestionsToAsk: UIEventSource<TagRenderingConfig[]> = new UIEventSource<
TagRenderingConfig[]
@ -95,6 +104,8 @@
let skipped: number = 0
let loginEnabled = state.featureSwitches.featureSwitchEnableLogin
let debug = state.featureSwitches.featureSwitchIsDebugging
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
@ -117,43 +128,84 @@
class="marker-questionbox-root"
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}
<div class="thanks">
<Tr t={Translations.t.general.questionBox.done} />
</div>
{/if}
<div class="mt-4 mb-8">
{#if skipped + answered > 0}
<div class="thanks">
<Tr t={Translations.t.general.questionBox.done} />
</div>
{#if answered === 0}
{#if skipped === 1}
<Tr t={Translations.t.general.questionBox.skippedOne} />
{:else}
<Tr t={Translations.t.general.questionBox.skippedMultiple.Subs({ skipped })} />
{/if}
{:else if answered === 1}
{#if skipped === 0}
<Tr t={Translations.t.general.questionBox.answeredOne} />
<div class="flex justify-center">
{#if answered === 0}
{#if skipped === 1}
<Tr t={Translations.t.general.questionBox.skippedOne} />
{:else}
<Tr t={Translations.t.general.questionBox.skippedMultiple.Subs({ skipped })} />
{/if}
{:else if answered === 1}
{#if skipped === 0}
<Tr t={Translations.t.general.questionBox.answeredOne} />
{:else if skipped === 1}
<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}
<Tr t={Translations.t.general.questionBox.answeredOneSkippedOne} />
<Tr
t={Translations.t.general.questionBox.answeredMultipleSkippedOne.Subs({ answered })}
/>
{: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}
<Tr
t={Translations.t.general.questionBox.answeredMultipleSkippedOne.Subs({ answered })}
/>
{:else}
<Tr
t={Translations.t.general.questionBox.answeredMultipleSkippedMultiple.Subs({
t={Translations.t.general.questionBox.answeredMultipleSkippedMultiple.Subs({
answered,
skipped,
})}
/>
{/if}
/>
{/if}
</div>
{#if skipped > 0}
{#if skipped + $skippedQuestions.size > 0}
<button
class="w-full"
on:click={() => {
@ -163,45 +215,25 @@
>
<Tr t={Translations.t.general.questionBox.reactivate} />
</button>
{/if}
{/if}
{:else}
<div>
{#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)
{#if $skippedQuestions.size - skipped > 0}
<button
class="w-full"
on:click={() => {
skippedQuestions.setData(new Set())
skipped = 0
}}
>
<button
class="secondary"
on:click={() => {
skip($firstQuestion)
}}
slot="cancel"
>
<Tr t={Translations.t.general.skip} />
</button>
</TagRenderingQuestionDynamic>
{/if}
</div>
{/if}
>
Show the disabled questions for this object
</button>
{/if}
{#if $debug}
Skipped questions are {Array.from($skippedQuestions).join(", ")}
{/if}
</div>
</div>
{/if}

View file

@ -36,6 +36,8 @@
import { Modal } from "flowbite-svelte"
import Popup from "../../Base/Popup.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 tags: UIEventSource<Record<string, string>>
@ -338,10 +340,41 @@
.then((changes) => state.changes.applyChanges(changes))
.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>
{#if question !== undefined}
<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
class="relative flex flex-col overflow-y-auto px-4"
style="max-height: 75vh"
@ -525,7 +558,7 @@
<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}/>
<Tr t={Translations.t.unknown.removedKeys} />
{#each $settableKeys as key}
<code>
<del>

View file

@ -97,6 +97,7 @@ import ClearCaches from "./Popup/ClearCaches.svelte"
import GroupedView from "./Popup/GroupedView.svelte"
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import NoteCommentElement from "./Popup/Notes/NoteCommentElement.svelte"
import DisabledQuestions from "./Popup/DisabledQuestions.svelte"
class NearbyImageVis implements SpecialVisualization {
// 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))