diff --git a/Docs/Making_Your_Own_Theme.md b/Docs/Making_Your_Own_Theme.md
index e07fea8df..9b206e445 100644
--- a/Docs/Making_Your_Own_Theme.md
+++ b/Docs/Making_Your_Own_Theme.md
@@ -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": "
Technical questions
The following questions are very technical!
{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
diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
index e00bf8487..75f91018c 100644
--- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
+++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts
@@ -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,
diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts
index e9d74f377..c1374b7ee 100644
--- a/Models/ThemeConfig/TagRenderingConfig.ts
+++ b/Models/ThemeConfig/TagRenderingConfig.ts
@@ -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;
diff --git a/Models/ThemeConfig/WithContextLoader.ts b/Models/ThemeConfig/WithContextLoader.ts
index 4290d73ef..ed9e2580a 100644
--- a/Models/ThemeConfig/WithContextLoader.ts
+++ b/Models/ThemeConfig/WithContextLoader.ts
@@ -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());
diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts
index 16e222358..181cd5ea6 100644
--- a/UI/Input/ValidatedTextField.ts
+++ b/UI/Input/ValidatedTextField.ts
@@ -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"),
diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts
index f2e018dc1..b6d79abd5 100644
--- a/UI/Popup/FeatureInfoBox.ts
+++ b/UI/Popup/FeatureInfoBox.ts
@@ -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([["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
diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts
index c38ce7806..5a857eef2 100644
--- a/UI/Popup/QuestionBox.ts
+++ b/UI/Popup/QuestionBox.ts
@@ -14,14 +14,19 @@ import Lazy from "../Base/Lazy";
*/
export default class QuestionBox extends VariableUiElement {
public readonly skippedQuestions: UIEventSource;
- public readonly currentQuestion: UIEventSource;
+ public readonly restingQuestions: UIEventSource;
- constructor(options: { tagsSource: UIEventSource, tagRenderings: TagRenderingConfig[], units: Unit[] }) {
+ constructor(options: {
+ tagsSource: UIEventSource,
+ tagRenderings: TagRenderingConfig[], units: Unit[],
+ showAllQuestionsAtOnce?: boolean | UIEventSource
+ }) {
const skippedQuestions: UIEventSource = new UIEventSource([])
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 = 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 = 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
}
diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json
index 05c840ab6..b96c4ae41 100644
--- a/assets/layers/charging_station/charging_station.json
+++ b/assets/layers/charging_station/charging_station.json
@@ -3384,6 +3384,12 @@
"render": {
"en": "Technical questions
The questions below are very technical. Feel free to ignore them
{questions}",
"nl": "Technische vragen
De vragen hieronder zijn erg technisch - sla deze over indien je hier geen tijd voor hebt
{questions}"
+ },
+ "freeform": {
+ "key": "questions",
+ "helperArgs": {
+ "showAllQuestions": true
+ }
}
}
],
diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json
index 16924328e..4415089c4 100644
--- a/assets/tagRenderings/questions.json
+++ b/assets/tagRenderings/questions.json
@@ -1,4 +1,7 @@
{
+ "questions": {
+ "id": "questions"
+ },
"images": {
"render": "{image_carousel()}{image_upload()}"
},
diff --git a/css/tagrendering.css b/css/tagrendering.css
index 7b675e563..fa8106f58 100644
--- a/css/tagrendering.css
+++ b/css/tagrendering.css
@@ -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;
}