forked from MapComplete/MapComplete
Feat: allow to disable questions (and to enable them again), fix #256
This commit is contained in:
parent
f8ef32f123
commit
93ebdd8e16
8 changed files with 238 additions and 76 deletions
|
@ -738,6 +738,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "disabled-questions",
|
||||
"render": {
|
||||
"special": {
|
||||
"type": "disabled_questions"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "title-privacy-legal",
|
||||
"render": {
|
||||
|
|
|
@ -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, [])
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
23
src/UI/Popup/DisabledQuestions.svelte
Normal file
23
src/UI/Popup/DisabledQuestions.svelte
Normal 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}
|
45
src/UI/Popup/DisabledQuestionsLayer.svelte
Normal file
45
src/UI/Popup/DisabledQuestionsLayer.svelte
Normal 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}
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue