Add the possibility to show all questions of a group as one + documentation update

This commit is contained in:
pietervdvn 2021-11-12 01:44:13 +01:00
parent 3570cfbaa8
commit 519feaa54b
10 changed files with 140 additions and 34 deletions

View file

@ -112,6 +112,61 @@ A JSON-schema file is available in Docs/Schemas - use LayoutConfig.schema.json t
There are few tags available that are calculated for convenience - e.g. the country an object is located
at. [An overview of all these metatags is available here](Docs/CalculatedTags.md)
### TagRendering groups
A tagRendering can have a `group`-attribute, which acts as a tag.
All tagRenderings with the same group name will be rendered together, in the same order as they were defined.
For example, if the defined tagrenderings have groups `A A B A A B B B`, the group order is `A B` and first all tagrenderings from group A will be rendered (thus numbers 0, 1, 3 and 4) followed by the question box for this group.
Then, all the tagRenderings for group B will be shown, thus number 2, 5, 6 and 7, again followed by their questionbox.
Additionally, every tagrendering will receive a the groupname as class in the HTML, which can be used to hook up custom CSS.
If no group tag is given, the group is `` (empty string)
### Deciding the questions position
By default, the questions are shown just beneath their group.
To override this behaviour, one can add a tagrendering with id `questions` to move the questions up.
To add a title to the questions, one can add a `render` and a condition.
To change the behaviour of the questionbox to show _all_ questions at once, one can use a helperArgs in the freeform field with option `showAllQuestions`.
For example, to show the questions on top, use:
```
"tagRenderings": [
{ "id": "questions" }
{ ... some tagrendering ... }
{ ... more tagrendering ...}
]
```
To show _all_ the questions of a group at once in the middle of the tagrenderings, with a header, use:
```
"tagRenderings": [
{
"id": "questions" ,
"group": "groupname",
"render": {
"en": "<h3>Technical questions</h3>The following questions are very technical!<br />{questions}
},
"freeform": {
"key": "questions",
"helperArgs": {
"showAllQuestions": true
}
}
}
{ ... some tagrendering ... }
{ ... more tagrendering ...}
]
```
Some hints
------------
@ -175,16 +230,7 @@ Instead, make one layer for one kind of object and change the icon based on attr
Using layers as filters - this doesn't work!
_All_ data is downloaded in one go and cached locally first. The layer selection (bottom left of the live app) then
selects _anything_ that matches the criteria. This match is then passed of to the rendering layer, which selects the
layer independently. This means that a feature can show up, even if it's layer is unselected!
For example, in the [cyclofix-theme](https://mapcomplete.osm.org/cyclofix), there is the layer with _bike-wash_ for do
it yourself bikecleaning - points marked with `service:bicycle:cleaning`. However, a bicycle repair shop can offer this
service too!
If all the layers are deselected except the bike wash layer, a shop having this tag will still match and will still show
up as shop.
Use the `filter`-functionality instead
### Not reading the .JSON-specs

View file

@ -8,7 +8,9 @@ export interface TagRenderingConfigJson {
/**
* The id of the tagrendering, should be an unique string.
* Used to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise
* Used to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.
*
* Use 'questions' to trigger the question box of this group (if a group is defined)
*/
id?: string,

View file

@ -104,6 +104,12 @@ export default class TagRenderingConfig {
throw `Freeform.args is defined. This should probably be 'freeform.helperArgs' (at ${context})`
}
if(json.freeform.key === "questions"){
if(this.id !== "questions"){
throw `If you use a freeform key 'questions', the ID must be 'questions' too to trigger the special behaviour. The current id is '${this.id}' (at ${context})`
}
}
if (ValidatedTextField.AllTypes[this.freeform.type] === undefined) {
@ -186,6 +192,9 @@ export default class TagRenderingConfig {
throw `${context}: The rendering for language ${ln} does not contain {questions}. This is a bug, as this rendering should include exactly this to trigger those questions to be shown!`
}
if(this.freeform?.key !== undefined && this.freeform?.key !== "questions"){
throw `${context}: If the ID is questions to trigger a question box, the only valid freeform value is 'questions' as well. Set freeform to questions or remove the freeform all together`
}
}
@ -201,6 +210,9 @@ export default class TagRenderingConfig {
if(txt.indexOf("{"+this.freeform.key+"}") >= 0){
continue
}
if(txt.indexOf("{"+this.freeform.key+":") >= 0){
continue
}
if(txt.indexOf("{canonical("+this.freeform.key+")") >= 0){
continue
}
@ -363,6 +375,9 @@ export default class TagRenderingConfig {
}
}
if(this.id === "questions"){
return this.render
}
if (this.freeform?.key === undefined) {
return this.render;

View file

@ -77,12 +77,6 @@ export default class WithContextLoader {
if (renderingJson["builtin"] !== undefined) {
const renderingId = renderingJson["builtin"]
if (renderingId === "questions") {
const tr = new TagRenderingConfig("questions", context);
renderings.push(tr)
continue;
}
let sharedJson = WithContextLoader.getKnownTagRenderings(renderingId)
if (sharedJson === undefined) {
const keys = Array.from(SharedTagRenderings.SharedTagRenderingJson.keys());

View file

@ -187,10 +187,10 @@ class OpeningHoursTextField implements TextFieldDef {
return new OpeningHoursInput(value, prefix, postfix)
}
}
export default class ValidatedTextField {
public static tpList: TextFieldDef[] = [
ValidatedTextField.tp(
"string",
"A basic string"),

View file

@ -18,6 +18,7 @@ import {Utils} from "../../Utils";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import MoveWizard from "./MoveWizard";
import Toggle from "../Input/Toggle";
import {FixedUiElement} from "../Base/FixedUiElement";
export default class FeatureInfoBox extends ScrollableFullScreen {
@ -56,9 +57,16 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
const allGroupNames = Utils.Dedup(layerConfig.tagRenderings.map(tr => tr.group))
if (State.state.featureSwitchUserbadge.data) {
const questionSpecs = layerConfig.tagRenderings.filter(tr => tr.id === "questions")
for (const groupName of allGroupNames) {
const questions = layerConfig.tagRenderings.filter(tr => tr.group === groupName)
const questionBox = new QuestionBox({tagsSource: tags, tagRenderings: questions, units:layerConfig.units});
const questionSpec = questionSpecs.filter(tr => tr.group === groupName)[0]
const questionBox = new QuestionBox({
tagsSource: tags,
tagRenderings: questions,
units: layerConfig.units,
showAllQuestionsAtOnce: questionSpec?.freeform?.helperArgs["showAllQuestions"] ?? State.state.featureSwitchShowAllQuestions
});
questionBoxes.set(groupName, questionBox)
}
}
@ -75,21 +83,22 @@ export default class FeatureInfoBox extends ScrollableFullScreen {
// This is a question box!
const questionBox = questionBoxes.get(tr.group)
questionBoxes.delete(tr.group)
if(tr.render !== undefined){
if (tr.render !== undefined) {
questionBox.SetClass("text-sm")
const renderedQuestion = new TagRenderingAnswer(tags, tr, tr.group + " questions", "", {
specialViz: new Map<string, BaseUIElement>([["questions", questionBox]])
})
const possiblyHidden = new Toggle(
renderedQuestion,
undefined,
questionBox.currentQuestion.map(i => i !== undefined)
questionBox.restingQuestions.map(ls => ls?.length > 0)
)
renderingsForGroup.push(possiblyHidden)
}else{
} else {
renderingsForGroup.push(questionBox)
}
} else {
let classes = innerClasses
let isHeader = renderingsForGroup.length === 0 && i > 0

View file

@ -14,14 +14,19 @@ import Lazy from "../Base/Lazy";
*/
export default class QuestionBox extends VariableUiElement {
public readonly skippedQuestions: UIEventSource<number[]>;
public readonly currentQuestion: UIEventSource<number | undefined>;
public readonly restingQuestions: UIEventSource<BaseUIElement[]>;
constructor(options: { tagsSource: UIEventSource<any>, tagRenderings: TagRenderingConfig[], units: Unit[] }) {
constructor(options: {
tagsSource: UIEventSource<any>,
tagRenderings: TagRenderingConfig[], units: Unit[],
showAllQuestionsAtOnce?: boolean | UIEventSource<boolean>
}) {
const skippedQuestions: UIEventSource<number[]> = new UIEventSource<number[]>([])
const tagsSource = options.tagsSource
const units = options.units
options.showAllQuestionsAtOnce = options.showAllQuestionsAtOnce ?? false
const tagRenderings = options.tagRenderings
.filter(tr => tr.question !== undefined)
.filter(tr => tr.question !== null)
@ -50,8 +55,7 @@ export default class QuestionBox extends VariableUiElement {
.onClick(() => {
skippedQuestions.setData([]);
})
const currentQuestion: UIEventSource<number | undefined> = tagsSource.map(tags => {
tagsSource.map(tags => {
if (tags === undefined) {
return undefined;
}
@ -74,13 +78,40 @@ export default class QuestionBox extends VariableUiElement {
return i
}
return undefined; // The questions are depleted
}, [skippedQuestions]);
const questionsToAsk: UIEventSource<BaseUIElement[]> = tagsSource.map(tags => {
if (tags === undefined) {
return [];
}
const qs = []
for (let i = 0; i < tagRenderingQuestions.length; i++) {
let tagRendering = tagRenderings[i];
if (skippedQuestions.data.indexOf(i) >= 0) {
continue;
}
if (tagRendering.IsKnown(tags)) {
continue;
}
if (tagRendering.condition &&
!tagRendering.condition.matchesProperties(tags)) {
// Filtered away by the condition, so it is kindof known
continue;
}
// this value is NOT known - this is the question we have to show!
qs.push(tagRenderingQuestions[i])
}
return qs
}, [skippedQuestions])
super(currentQuestion.map(i => {
super(questionsToAsk.map(allQuestions => {
const els: BaseUIElement[] = []
if (i !== undefined) {
els.push(tagRenderingQuestions[i])
if (options.showAllQuestionsAtOnce === true || options.showAllQuestionsAtOnce["data"]) {
els.push(...questionsToAsk.data)
} else {
els.push(allQuestions[0])
}
if (skippedQuestions.data.length > 0) {
@ -92,7 +123,7 @@ export default class QuestionBox extends VariableUiElement {
)
this.skippedQuestions = skippedQuestions;
this.currentQuestion = currentQuestion
this.restingQuestions = questionsToAsk
}

View file

@ -3384,6 +3384,12 @@
"render": {
"en": "<h3>Technical questions</h3>The questions below are very technical. Feel free to ignore them<br/>{questions}",
"nl": "<h3>Technische vragen</h3>De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt<br/>{questions}"
},
"freeform": {
"key": "questions",
"helperArgs": {
"showAllQuestions": true
}
}
}
],

View file

@ -1,4 +1,7 @@
{
"questions": {
"id": "questions"
},
"images": {
"render": "{image_carousel()}{image_upload()}"
},

View file

@ -11,7 +11,7 @@
color: var(--subtle-detail-color-contrast);
padding: 1em;
border-radius: 1em;
font-size: larger;
font-size: larger !important;
overflow-wrap: initial;
}