forked from MapComplete/MapComplete
Themes: allow a questionbox to have both a whitelist and a blacklist
This commit is contained in:
parent
0c82fd5438
commit
f88fade35b
2 changed files with 87 additions and 32 deletions
|
@ -170,7 +170,7 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
|
||||||
|
|
||||||
export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("AddQuestionBox", "Adds a 'questions'-object if no question element is added yet")
|
super("AddQuestionBox", "Adds a 'questions'-object if no question element is added yet. Will ignore all elements which were previously asked for (and questions labeled with 'hidden')")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,43 +202,46 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
||||||
(sp) => sp.args.length === 0 || sp.args[0].trim() === ""
|
(sp) => sp.args.length === 0 || sp.args[0].trim() === ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if (noLabels.length > 1) {
|
if (noLabels.length > 1) {
|
||||||
context.err(
|
context.err(
|
||||||
"Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this. Did you perhaps import all questions from another layer?"
|
"Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this - questions will be shown twice. Did you perhaps import all questions from another layer?",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We want to construct a questionbox that shows all leftover questions.
|
||||||
|
* For this, we need to determine what those leftover questions _are_ in the first place.
|
||||||
|
*
|
||||||
|
* So, we gather the labels of the layer and compare that to the labels used by previous question boxes
|
||||||
|
*/
|
||||||
|
|
||||||
// ALl labels that are used in this layer
|
// ALl labels that are used in this layer
|
||||||
const allLabels = new Set(
|
const allLabels = new Set(
|
||||||
[].concat(
|
json.tagRenderings.flatMap(
|
||||||
...json.tagRenderings.map(
|
|
||||||
(tr) => (<QuestionableTagRenderingConfigJson>tr).labels ?? []
|
(tr) => (<QuestionableTagRenderingConfigJson>tr).labels ?? []
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
const seen: Set<string> = new Set()
|
/**
|
||||||
|
* The essence of all questionboxes: what is whitelisted, what is blacklisted?
|
||||||
|
*/
|
||||||
|
const questionBoxes: { blacklist: string[], whitelist: string[] }[] = []
|
||||||
for (const questionSpecial of questionSpecials) {
|
for (const questionSpecial of questionSpecials) {
|
||||||
if (typeof questionSpecial === "string") {
|
if (typeof questionSpecial === "string") {
|
||||||
|
// Probably a header or something
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const used = questionSpecial.args[0]
|
const whitelist = questionSpecial.args[0]
|
||||||
?.split(";")
|
?.split(";")
|
||||||
?.map((a) => a.trim())
|
?.map((a) => a.trim())
|
||||||
?.filter((s) => s != "")
|
?.filter((s) => s != "")
|
||||||
const blacklisted = questionSpecial.args[1]
|
const blacklist = questionSpecial.args[1]
|
||||||
?.split(";")
|
?.split(";")
|
||||||
?.map((a) => a.trim())
|
?.map((a) => a.trim())
|
||||||
?.filter((s) => s != "")
|
?.filter((s) => s != "")
|
||||||
if (blacklisted?.length > 0 && used?.length > 0) {
|
|
||||||
context.err(
|
for (const usedLabel of whitelist) {
|
||||||
"The {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
|
|
||||||
"\n Whitelisted: " +
|
|
||||||
used.join(", ") +
|
|
||||||
"\n Blacklisted: " +
|
|
||||||
blacklisted.join(", ")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for (const usedLabel of used) {
|
|
||||||
if (!allLabels.has(usedLabel)) {
|
if (!allLabels.has(usedLabel)) {
|
||||||
context.err(
|
context.err(
|
||||||
"This layers specifies a special question element for label `" +
|
"This layers specifies a special question element for label `" +
|
||||||
|
@ -248,24 +251,76 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
||||||
Array.from(allLabels).join(", ")
|
Array.from(allLabels).join(", ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
seen.add(usedLabel)
|
|
||||||
}
|
}
|
||||||
|
questionBoxes.push({ blacklist, whitelist })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noLabels.length == 0) {
|
if (noLabels.length == 0) {
|
||||||
/* At this point, we know which question labels are not yet handled and which already are handled, and we
|
// We already have a questionbox handling _all_ questions
|
||||||
* know there is no previous catch-all questions
|
return json
|
||||||
*/
|
|
||||||
seen.add("hidden")
|
|
||||||
const question: QuestionableTagRenderingConfigJson = {
|
|
||||||
id: "leftover-questions",
|
|
||||||
labels: ["ignore-docs", "added_by_default"],
|
|
||||||
render: {
|
|
||||||
"*": `{questions( ,${Array.from(seen).join(";")})}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
json.tagRenderings.push(question)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const usedLabels: Set<string> = new Set()
|
||||||
|
|
||||||
|
for (const { blacklist, whitelist } of questionBoxes) {
|
||||||
|
if (whitelist.length > 0 && blacklist.length == 0) {
|
||||||
|
// All questions from "whitelist" are guaranteed to be used here
|
||||||
|
whitelist.forEach(label => usedLabels.add(label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** We should still check the weird questionboxes that have both a whitelist _and_ a blacklist.
|
||||||
|
* Can we say that the whitelisted items are fully consumed?
|
||||||
|
*/
|
||||||
|
let needsEvaluation = true
|
||||||
|
let toEvaluate = questionBoxes.filter(q => q.whitelist.length > 0 && q.blacklist.length > 0)
|
||||||
|
while (needsEvaluation && toEvaluate.length > 0) {
|
||||||
|
needsEvaluation = false
|
||||||
|
const toReEvaluate = []
|
||||||
|
for (const { blacklist, whitelist } of toEvaluate) {
|
||||||
|
const blacklistRest = blacklist.filter(label => !usedLabels.has(label))
|
||||||
|
if (blacklistRest.length == 0) {
|
||||||
|
// All items from the blacklist have been handled by a different questionbox
|
||||||
|
// We can safely say that all whitelisted items are consumed
|
||||||
|
if (whitelist.length == 0) {
|
||||||
|
// Even better: this questionbox will show all leftover questions
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
whitelist.forEach(label => {
|
||||||
|
usedLabels.add(label)
|
||||||
|
})
|
||||||
|
needsEvaluation = true
|
||||||
|
} else {
|
||||||
|
// Hmm, maybe in a next iteration?
|
||||||
|
toReEvaluate.push({ blacklist, whitelist })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toEvaluate = toReEvaluate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toEvaluate.length > 0) {
|
||||||
|
// If we end up here, we have a questionbox with a whitelist _and_ a blacklist.
|
||||||
|
// We cannot unambiguously create a leftover-questions box for this
|
||||||
|
|
||||||
|
context.err(
|
||||||
|
"Could not calculate a non-ambiguous leftover questions block. A {questions()}-special rendering is found which has both a whitelist and a blacklist; where the blacklist was not fully consumed by other tagRenderings\n\t" +
|
||||||
|
JSON.stringify(toEvaluate),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* At this point, we know which question labels are not yet handled and which already are handled, and we
|
||||||
|
* know there is no previous catch-all questions
|
||||||
|
*/
|
||||||
|
usedLabels.add("hidden")
|
||||||
|
const question: QuestionableTagRenderingConfigJson = {
|
||||||
|
id: "leftover-questions",
|
||||||
|
labels: ["ignore-docs", "added_by_default"],
|
||||||
|
render: {
|
||||||
|
"*": `{questions( ,${Array.from(usedLabels).join(";")})}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
json.tagRenderings.push(question)
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class QuestionViz implements SpecialVisualizationSvelte {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "blacklisted-labels",
|
name: "blacklisted-labels",
|
||||||
doc: "One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'",
|
doc: "One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'. If both a whitelist and a blacklist are given, will show questions having at least one label from the whitelist but none of the blacklist.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "show_all",
|
name: "show_all",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue