Refactoring: refactoring of all Conversions
This commit is contained in:
parent
4e8dfc0026
commit
f2863cdf17
38 changed files with 1177 additions and 1269 deletions
|
@ -1169,15 +1169,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1196,7 +1196,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -1365,15 +1365,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1392,7 +1392,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1156,15 +1156,15 @@ export default {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1183,7 +1183,7 @@ export default {
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -1351,15 +1351,15 @@ export default {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1378,7 +1378,7 @@ export default {
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1076,15 +1076,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1103,7 +1103,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -1272,15 +1272,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1299,7 +1299,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1063,15 +1063,15 @@ export default {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1090,7 +1090,7 @@ export default {
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -1258,15 +1258,15 @@ export default {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -1285,7 +1285,7 @@ export default {
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,15 +21,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -48,7 +48,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,15 +21,15 @@ export default {
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -48,7 +48,7 @@ export default {
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -13,7 +13,9 @@ export default abstract class Script {
|
|||
ScriptUtils.fixUtils()
|
||||
const args = [...process.argv]
|
||||
args.splice(0, 2)
|
||||
this.main(args).then((_) => console.log("All done"))
|
||||
this.main(args)
|
||||
.then((_) => console.log("All done"))
|
||||
.catch((e) => console.log("ERROR:", e))
|
||||
}
|
||||
|
||||
public printHelp() {
|
||||
|
|
|
@ -14,13 +14,18 @@ import {
|
|||
import { Translation } from "../src/UI/i18n/Translation"
|
||||
import { PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer"
|
||||
import { PrepareTheme } from "../src/Models/ThemeConfig/Conversion/PrepareTheme"
|
||||
import { DesugaringContext } from "../src/Models/ThemeConfig/Conversion/Conversion"
|
||||
import {
|
||||
ConversionContext,
|
||||
DesugaringContext,
|
||||
} from "../src/Models/ThemeConfig/Conversion/Conversion"
|
||||
import { Utils } from "../src/Utils"
|
||||
import Script from "./Script"
|
||||
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
|
||||
import { parse as parse_html } from "node-html-parser"
|
||||
import { ExtraFunctions } from "../src/Logic/ExtraFunctions"
|
||||
import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
import LayerConfig from "../src/Models/ThemeConfig/LayerConfig"
|
||||
import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig"
|
||||
// This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
|
||||
// It spits out an overview of those to be used to load them
|
||||
|
||||
|
@ -307,7 +312,7 @@ class LayerOverviewUtils extends Script {
|
|||
layers: ScriptUtils.getLayerFiles().map((f) => f.parsed),
|
||||
themes: ScriptUtils.getThemeFiles().map((f) => f.parsed),
|
||||
},
|
||||
"GenerateLayerOverview:"
|
||||
ConversionContext.construct([], [])
|
||||
)
|
||||
|
||||
if (AllSharedLayers.getSharedLayersConfigs().size == 0) {
|
||||
|
@ -329,8 +334,13 @@ class LayerOverviewUtils extends Script {
|
|||
} catch (e) {
|
||||
throw "Could not parse or read file " + sharedLayerPath
|
||||
}
|
||||
const context = "While building builtin layer " + sharedLayerPath
|
||||
const fixed = prepLayer.convertStrict(parsed, context)
|
||||
if (parsed === undefined) {
|
||||
throw "File " + sharedLayerPath + " yielded undefined"
|
||||
}
|
||||
const fixed = prepLayer.convertStrict(
|
||||
parsed,
|
||||
ConversionContext.construct([sharedLayerPath], ["PrepareLayer"])
|
||||
)
|
||||
|
||||
if (!fixed.source) {
|
||||
console.error(sharedLayerPath, "has no source configured:", fixed)
|
||||
|
@ -346,7 +356,10 @@ class LayerOverviewUtils extends Script {
|
|||
}
|
||||
|
||||
const validator = new ValidateLayer(sharedLayerPath, true, doesImageExist)
|
||||
validator.convertStrict(fixed, context)
|
||||
validator.convertStrict(
|
||||
fixed,
|
||||
ConversionContext.construct([sharedLayerPath], ["PrepareLayer"])
|
||||
)
|
||||
|
||||
return fixed
|
||||
}
|
||||
|
@ -386,12 +399,35 @@ class LayerOverviewUtils extends Script {
|
|||
const fixed = this.parseLayer(doesImageExist, prepLayer, sharedLayerPath)
|
||||
|
||||
if (sharedLayers.has(fixed.id)) {
|
||||
throw "There are multiple layers with the id " + fixed.id
|
||||
throw "There are multiple layers with the id " + fixed.id + ", " + sharedLayerPath
|
||||
}
|
||||
|
||||
sharedLayers.set(fixed.id, fixed)
|
||||
recompiledLayers.push(fixed.id)
|
||||
|
||||
{
|
||||
// Add a summary of the icon
|
||||
const layerConfig = new LayerConfig(fixed, "generating_icon")
|
||||
const pointRendering: PointRenderingConfig = layerConfig.mapRendering.find((pr) =>
|
||||
pr.location.has("point")
|
||||
)
|
||||
const defaultTags = layerConfig.GetBaseTags()
|
||||
fixed["_layerIcon"] = Utils.NoNull(
|
||||
(pointRendering?.marker ?? []).map((i) => {
|
||||
const icon = i.icon?.GetRenderValue(defaultTags)?.txt
|
||||
if (!icon) {
|
||||
return undefined
|
||||
}
|
||||
const result = { icon }
|
||||
const c = i.color?.GetRenderValue(defaultTags)?.txt
|
||||
if (c) {
|
||||
result["color"] = c
|
||||
}
|
||||
return result
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
this.writeLayer(fixed)
|
||||
}
|
||||
|
||||
|
@ -594,16 +630,25 @@ class LayerOverviewUtils extends Script {
|
|||
|
||||
recompiledThemes.push(themeFile.id)
|
||||
|
||||
new PrevalidateTheme().convertStrict(themeFile, themePath)
|
||||
new PrevalidateTheme().convertStrict(
|
||||
themeFile,
|
||||
ConversionContext.construct([themePath], ["PrepareLayer"])
|
||||
)
|
||||
try {
|
||||
themeFile = new PrepareTheme(convertState).convertStrict(themeFile, themePath)
|
||||
themeFile = new PrepareTheme(convertState).convertStrict(
|
||||
themeFile,
|
||||
ConversionContext.construct([themePath], ["PrepareLayer"])
|
||||
)
|
||||
|
||||
new ValidateThemeAndLayers(
|
||||
new DoesImageExist(licensePaths, existsSync, knownTagRenderings),
|
||||
themePath,
|
||||
true,
|
||||
knownTagRenderings
|
||||
).convertStrict(themeFile, themePath)
|
||||
).convertStrict(
|
||||
themeFile,
|
||||
ConversionContext.construct([themePath], ["PrepareLayer"])
|
||||
)
|
||||
|
||||
if (themeFile.icon.endsWith(".svg")) {
|
||||
try {
|
||||
|
|
|
@ -19,3 +19,10 @@ report.mapcomplete.org {
|
|||
to http://127.0.0.1:2600
|
||||
}
|
||||
}
|
||||
|
||||
studio.mapcomplete.org {
|
||||
reverse_proxy {
|
||||
to http://127.0.0.1:1235
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import * as fs from "node:fs"
|
||||
import * as http from "node:http"
|
||||
import * as path from "node:path"
|
||||
import { ReadStream } from "fs"
|
||||
import ScriptUtils from "./ScriptUtils"
|
||||
|
||||
const PORT = 1235
|
||||
|
@ -26,15 +25,10 @@ async function prepareFile(url: string): Promise<string> {
|
|||
const paths = [STATIC_PATH, url]
|
||||
if (url.endsWith("/")) paths.push("index.html")
|
||||
const filePath = path.join(...paths)
|
||||
const exists = fs.existsSync(filePath)
|
||||
console.log("Checking", filePath, exists)
|
||||
const found = exists
|
||||
if (!found) {
|
||||
return null
|
||||
if (fs.existsSync(filePath)) {
|
||||
return fs.readFileSync(filePath, "utf8")
|
||||
}
|
||||
const streamPath = filePath
|
||||
const ext = path.extname(streamPath).substring(1).toLowerCase()
|
||||
return fs.readFileSync(streamPath, "utf8")
|
||||
return null
|
||||
}
|
||||
|
||||
http.createServer(async (req, res) => {
|
||||
|
@ -61,7 +55,7 @@ http.createServer(async (req, res) => {
|
|||
fs.mkdirSync(dir)
|
||||
}
|
||||
}
|
||||
req.pipe(fs.createWriteStream(STATIC_PATH + paths.join("/") + ".new.json"))
|
||||
req.pipe(fs.createWriteStream(STATIC_PATH + paths.join("/")))
|
||||
res.writeHead(200, { "Content-Type": MIME_TYPES.html })
|
||||
res.write("<html><body>OK</body></html>", "utf8")
|
||||
res.end()
|
||||
|
|
|
@ -24,12 +24,9 @@ import {
|
|||
ValidateThemeAndLayers,
|
||||
} from "../Models/ThemeConfig/Conversion/Validation"
|
||||
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
|
||||
import {
|
||||
MinimalTagRenderingConfigJson,
|
||||
TagRenderingConfigJson
|
||||
} from "../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
||||
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
|
||||
import Hash from "./Web/Hash"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
|
||||
|
||||
export default class DetermineLayout {
|
||||
private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))
|
||||
|
@ -168,8 +165,12 @@ export default class DetermineLayout {
|
|||
private static prepCustomTheme(json: any, sourceUrl?: string, forceId?: string): LayoutConfig {
|
||||
if (json.layers === undefined && json.tagRenderings !== undefined) {
|
||||
// We got fed a layer instead of a theme
|
||||
const layerConfig = <LayerConfigJson>json
|
||||
const iconTr: string | TagRenderingConfigJson = layerConfig.pointRendering.map((mr) => mr.marker.find(icon => icon.icon !== undefined).icon).find((i) => i !== undefined)
|
||||
const layerConfig = <LayerConfigJson>json
|
||||
const iconTr: string | TagRenderingConfigJson = <any>(
|
||||
layerConfig.pointRendering
|
||||
.map((mr) => mr.marker.find((icon) => icon.icon !== undefined).icon)
|
||||
.find((i) => i !== undefined)
|
||||
)
|
||||
const icon = new TagRenderingConfig(iconTr).render.txt
|
||||
json = {
|
||||
id: json.id,
|
||||
|
@ -193,34 +194,25 @@ export default class DetermineLayout {
|
|||
sharedLayers: knownLayersDict,
|
||||
publicLayers: new Set<string>(),
|
||||
}
|
||||
json = new FixLegacyTheme().convertStrict(json, "While loading a dynamic theme")
|
||||
json = new FixLegacyTheme().convertStrict(json)
|
||||
const raw = json
|
||||
|
||||
json = new FixImages(DetermineLayout._knownImages).convertStrict(
|
||||
json,
|
||||
"While fixing the images"
|
||||
)
|
||||
json = new FixImages(DetermineLayout._knownImages).convertStrict(json)
|
||||
json.enableNoteImports = json.enableNoteImports ?? false
|
||||
json = new PrepareTheme(convertState).convertStrict(json, "While preparing a dynamic theme")
|
||||
json = new PrepareTheme(convertState).convertStrict(json)
|
||||
console.log("The layoutconfig is ", json)
|
||||
|
||||
json.id = forceId ?? json.id
|
||||
|
||||
{
|
||||
let { errors } = new PrevalidateTheme().convert(json, "validation")
|
||||
if (errors.length > 0) {
|
||||
throw "Detected errors: " + errors.join("\n")
|
||||
}
|
||||
new PrevalidateTheme().convertStrict(json)
|
||||
}
|
||||
{
|
||||
let { errors } = new ValidateThemeAndLayers(
|
||||
new ValidateThemeAndLayers(
|
||||
new DoesImageExist(new Set<string>(), (_) => true),
|
||||
"",
|
||||
false
|
||||
).convert(json, "validation")
|
||||
if (errors.length > 0) {
|
||||
throw "Detected errors: " + errors.join("\n")
|
||||
}
|
||||
).convertStrict(json)
|
||||
}
|
||||
return new LayoutConfig(json, false, {
|
||||
definitionRaw: JSON.stringify(raw, null, " "),
|
||||
|
|
|
@ -308,9 +308,6 @@ export class RegexTag extends TagsFilter {
|
|||
if (typeof this.value === "string") {
|
||||
return [{ k: this.key, v: this.value }]
|
||||
}
|
||||
if (this.value.toString() != "/^..*$/" || this.value.toString() != ".+") {
|
||||
console.warn("Regex value in tag; using wildcard:", this.key, this.value)
|
||||
}
|
||||
return [{ k: this.key, v: undefined }]
|
||||
}
|
||||
console.error("Cannot export regex tag to asChange; ", this.key, this.value)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DesugaringStep } from "./Conversion"
|
||||
import { ConversionContext, DesugaringStep } from "./Conversion"
|
||||
import { Utils } from "../../../Utils"
|
||||
import Translations from "../../../UI/i18n/Translations"
|
||||
|
||||
|
@ -117,15 +117,12 @@ export class AddContextToTranslations<T> extends DesugaringStep<T> {
|
|||
* rewritten // => theme
|
||||
*
|
||||
*/
|
||||
convert(
|
||||
json: T,
|
||||
context: string
|
||||
): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
if (json["#dont-translate"] === "*") {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
|
||||
const result = Utils.WalkJson(
|
||||
return Utils.WalkJson(
|
||||
json,
|
||||
(leaf, path) => {
|
||||
if (leaf === undefined || leaf === null) {
|
||||
|
@ -149,9 +146,5 @@ export class AddContextToTranslations<T> extends DesugaringStep<T> {
|
|||
},
|
||||
(obj) => obj === undefined || obj === null || Translations.isProbablyATranslation(obj)
|
||||
)
|
||||
|
||||
return {
|
||||
result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import { Utils } from "../../../Utils"
|
||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
|
||||
|
@ -9,6 +8,91 @@ export interface DesugaringContext {
|
|||
publicLayers?: Set<string>
|
||||
}
|
||||
|
||||
export class ConversionContext {
|
||||
readonly path: ReadonlyArray<string | number>
|
||||
readonly operation: ReadonlyArray<string>
|
||||
readonly messages: ConversionMessage[] = []
|
||||
|
||||
private constructor(path: ReadonlyArray<string | number>, operation?: ReadonlyArray<string>) {
|
||||
this.path = path
|
||||
this.operation = operation ?? []
|
||||
}
|
||||
|
||||
public static construct(path: (string | number)[], operation: string[]) {
|
||||
return new ConversionContext([...path], [...operation])
|
||||
}
|
||||
|
||||
static print(msg: ConversionMessage) {
|
||||
if (msg.level === "error") {
|
||||
console.error(
|
||||
ConversionContext.red("ERR "),
|
||||
msg.context.path.join("."),
|
||||
ConversionContext.red(msg.message),
|
||||
msg.context.operation.join(".")
|
||||
)
|
||||
} else if (msg.level === "warning") {
|
||||
console.warn(
|
||||
ConversionContext.red("<!> "),
|
||||
msg.context.path.join("."),
|
||||
ConversionContext.yellow(msg.message),
|
||||
msg.context.operation.join(".")
|
||||
)
|
||||
} else {
|
||||
console.log(
|
||||
" ",
|
||||
msg.context.path.join("."),
|
||||
msg.message,
|
||||
msg.context.operation.join(".")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private static yellow(s) {
|
||||
return "\x1b[33m" + s + "\x1b[0m"
|
||||
}
|
||||
|
||||
private static red(s) {
|
||||
return "\x1b[31m" + s + "\x1b[0m"
|
||||
}
|
||||
|
||||
public enter(key: string | number | (string | number)[]) {
|
||||
if (!Array.isArray(key)) {
|
||||
return new ConversionContext([...this.path, key], this.operation)
|
||||
}
|
||||
return new ConversionContext([...this.path, ...key], this.operation)
|
||||
}
|
||||
|
||||
public enters(...key: (string | number)[]) {
|
||||
return this.enter(key)
|
||||
}
|
||||
|
||||
public inOperation(key: string) {
|
||||
return new ConversionContext(this.path, [...this.operation, key])
|
||||
}
|
||||
|
||||
warn(message: string) {
|
||||
this.messages.push({ context: this, level: "warning", message })
|
||||
}
|
||||
|
||||
err(message: string) {
|
||||
this.messages.push({ context: this, level: "error", message })
|
||||
}
|
||||
|
||||
info(message: string) {
|
||||
this.messages.push({ context: this, level: "information", message })
|
||||
}
|
||||
|
||||
public hasErrors() {
|
||||
return this.messages?.find((m) => m.level === "error") !== undefined
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConversionMessage {
|
||||
context: ConversionContext
|
||||
message: string
|
||||
level: "debug" | "information" | "warning" | "error"
|
||||
}
|
||||
|
||||
export abstract class Conversion<TIn, TOut> {
|
||||
public readonly modifiedAttributes: string[]
|
||||
public readonly name: string
|
||||
|
@ -20,52 +104,24 @@ export abstract class Conversion<TIn, TOut> {
|
|||
this.name = name
|
||||
}
|
||||
|
||||
public static strict<T>(fixed: {
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
result?: T
|
||||
}): T {
|
||||
fixed.information?.forEach((i) => console.log(" ", i))
|
||||
const yellow = (s) => "\x1b[33m" + s + "\x1b[0m"
|
||||
const red = (s) => "\x1b[31m" + s + "\x1b[0m"
|
||||
fixed.warnings?.forEach((w) => console.warn(red(`<!> `), yellow(w)))
|
||||
|
||||
if (fixed?.errors !== undefined && fixed?.errors?.length > 0) {
|
||||
fixed.errors?.forEach((e) => console.error(red(`ERR ` + e)))
|
||||
public convertStrict(json: TIn, context?: ConversionContext): TOut {
|
||||
context ??= ConversionContext.construct([], [])
|
||||
context = context.enter(this.name)
|
||||
const fixed = this.convert(json, context)
|
||||
for (const msg of context.messages) {
|
||||
ConversionContext.print(msg)
|
||||
}
|
||||
if (context.hasErrors()) {
|
||||
throw "Detected one or more errors, stopping now"
|
||||
}
|
||||
|
||||
return fixed.result
|
||||
}
|
||||
|
||||
public convertStrict(json: TIn, context: string): TOut {
|
||||
const fixed = this.convert(json, context)
|
||||
return DesugaringStep.strict(fixed)
|
||||
}
|
||||
|
||||
public convertJoin(
|
||||
json: TIn,
|
||||
context: string,
|
||||
errors: string[],
|
||||
warnings?: string[],
|
||||
information?: string[]
|
||||
): TOut {
|
||||
const fixed = this.convert(json, context)
|
||||
errors?.push(...(fixed.errors ?? []))
|
||||
warnings?.push(...(fixed.warnings ?? []))
|
||||
information?.push(...(fixed.information ?? []))
|
||||
return fixed.result
|
||||
return fixed
|
||||
}
|
||||
|
||||
public andThenF<X>(f: (tout: TOut) => X): Conversion<TIn, X> {
|
||||
return new Pipe(this, new Pure(f))
|
||||
}
|
||||
|
||||
abstract convert(
|
||||
json: TIn,
|
||||
context: string
|
||||
): { result: TOut; errors?: string[]; warnings?: string[]; information?: string[] }
|
||||
public abstract convert(json: TIn, context: ConversionContext): TOut
|
||||
}
|
||||
|
||||
export abstract class DesugaringStep<T> extends Conversion<T, T> {}
|
||||
|
@ -80,29 +136,12 @@ class Pipe<TIn, TInter, TOut> extends Conversion<TIn, TOut> {
|
|||
this._step1 = step1
|
||||
}
|
||||
|
||||
convert(
|
||||
json: TIn,
|
||||
context: string
|
||||
): { result: TOut; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
const r0 = this._step0.convert(json, context)
|
||||
const { result, errors, information, warnings } = r0
|
||||
if (result === undefined && errors.length > 0) {
|
||||
return {
|
||||
...r0,
|
||||
result: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
const r = this._step1.convert(result, context)
|
||||
Utils.PushList(errors, r.errors)
|
||||
Utils.PushList(warnings, r.warnings)
|
||||
Utils.PushList(information, r.information)
|
||||
return {
|
||||
result: r.result,
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
convert(json: TIn, context: ConversionContext): TOut {
|
||||
const r0 = this._step0.convert(json, context.inOperation(this._step0.name))
|
||||
if (context.hasErrors()) {
|
||||
return undefined
|
||||
}
|
||||
return this._step1.convert(r0, context.inOperation(this._step1.name))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,11 +153,8 @@ class Pure<TIn, TOut> extends Conversion<TIn, TOut> {
|
|||
this._f = f
|
||||
}
|
||||
|
||||
convert(
|
||||
json: TIn,
|
||||
context: string
|
||||
): { result: TOut; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
return { result: this._f(json) }
|
||||
convert(json: TIn, context: ConversionContext): TOut {
|
||||
return this._f(json)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,31 +170,19 @@ export class Each<X, Y> extends Conversion<X[], Y[]> {
|
|||
this._step = step
|
||||
}
|
||||
|
||||
convert(
|
||||
values: X[],
|
||||
context: string
|
||||
): { result: Y[]; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(values: X[], context: ConversionContext): Y[] {
|
||||
if (values === undefined || values === null) {
|
||||
return { result: undefined }
|
||||
return <undefined | null>values
|
||||
}
|
||||
const information: string[] = []
|
||||
const warnings: string[] = []
|
||||
const errors: string[] = []
|
||||
const step = this._step
|
||||
const result: Y[] = []
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const r = step.convert(values[i], context + "[" + i + "]")
|
||||
Utils.PushList(information, r.information)
|
||||
Utils.PushList(warnings, r.warnings)
|
||||
Utils.PushList(errors, r.errors)
|
||||
result.push(r.result)
|
||||
}
|
||||
return {
|
||||
information,
|
||||
errors,
|
||||
warnings,
|
||||
result,
|
||||
const context_ = context.enter(i).inOperation("each")
|
||||
const r = step.convert(values[i], context_)
|
||||
result.push(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,23 +204,17 @@ export class On<P, T> extends DesugaringStep<T> {
|
|||
this.key = key
|
||||
}
|
||||
|
||||
convert(
|
||||
json: T,
|
||||
context: string
|
||||
): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
json = { ...json }
|
||||
const step = this.step(json)
|
||||
const key = this.key
|
||||
const value: P = json[key]
|
||||
if (value === undefined || value === null) {
|
||||
return { result: json }
|
||||
}
|
||||
const r = step.convert(value, context + "." + key)
|
||||
json[key] = r.result
|
||||
return {
|
||||
...r,
|
||||
result: json,
|
||||
return undefined
|
||||
}
|
||||
|
||||
json[key] = step.convert(value, context.enter(key).inOperation("on[" + key + "]"))
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,13 +223,8 @@ export class Pass<T> extends Conversion<T, T> {
|
|||
super(message ?? "Does nothing, often to swap out steps in testing", [], "Pass")
|
||||
}
|
||||
|
||||
convert(
|
||||
json: T,
|
||||
context: string
|
||||
): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
return {
|
||||
result: json,
|
||||
}
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,25 +240,13 @@ export class Concat<X, T> extends Conversion<X[], T[]> {
|
|||
this._step = step
|
||||
}
|
||||
|
||||
convert(
|
||||
values: X[],
|
||||
context: string
|
||||
): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(values: X[], context: ConversionContext): T[] {
|
||||
if (values === undefined || values === null) {
|
||||
// Move on - nothing to see here!
|
||||
return {
|
||||
result: undefined,
|
||||
}
|
||||
}
|
||||
const r = new Each(this._step).convert(values, context)
|
||||
const vals: T[][] = r.result
|
||||
|
||||
const flattened: T[] = [].concat(...vals)
|
||||
|
||||
return {
|
||||
...r,
|
||||
result: flattened,
|
||||
return <undefined | null>values
|
||||
}
|
||||
const vals: T[][] = new Each(this._step).convert(values, context.inOperation("concat"))
|
||||
return [].concat(...vals)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,15 +262,12 @@ export class FirstOf<T, X> extends Conversion<T, X> {
|
|||
this._conversion = conversion
|
||||
}
|
||||
|
||||
convert(
|
||||
json: T,
|
||||
context: string
|
||||
): { result: X; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
const reslt = this._conversion.convert(json, context)
|
||||
return {
|
||||
...reslt,
|
||||
result: reslt.result[0],
|
||||
convert(json: T, context: ConversionContext): X {
|
||||
const values = this._conversion.convert(json, context.inOperation("firstOf"))
|
||||
if (values.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
return values[0]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,38 +285,24 @@ export class Fuse<T> extends DesugaringStep<T> {
|
|||
this.steps = Utils.NoNull(steps)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: T,
|
||||
context: string
|
||||
): { result: T; errors: string[]; warnings: string[]; information: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
const information = []
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
for (let i = 0; i < this.steps.length; i++) {
|
||||
const step = this.steps[i]
|
||||
try {
|
||||
let r = step.convert(json, "While running step " + step.name + ": " + context)
|
||||
if (r.result["tagRenderings"]?.some((tr) => tr === undefined)) {
|
||||
throw step.name + " introduced an undefined tagRendering"
|
||||
}
|
||||
errors.push(...(r.errors ?? []))
|
||||
warnings.push(...(r.warnings ?? []))
|
||||
information.push(...(r.information ?? []))
|
||||
json = r.result
|
||||
if (errors.length > 0) {
|
||||
const r = step.convert(json, context.inOperation(step.name))
|
||||
if (r === undefined) {
|
||||
break
|
||||
}
|
||||
if (context.hasErrors()) {
|
||||
break
|
||||
}
|
||||
json = r
|
||||
} catch (e) {
|
||||
console.error("Step " + step.name + " failed due to ", e, e.stack)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,14 +318,15 @@ export class SetDefault<T> extends DesugaringStep<T> {
|
|||
this._overrideEmptyString = overrideEmptyString
|
||||
}
|
||||
|
||||
convert(json: T, context: string): { result: T } {
|
||||
convert(json: T, context: ConversionContext): T {
|
||||
if (json === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (json[this.key] === undefined || (json[this.key] === "" && this._overrideEmptyString)) {
|
||||
json = { ...json }
|
||||
json[this.key] = this.value
|
||||
}
|
||||
|
||||
return {
|
||||
result: json,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Conversion } from "./Conversion"
|
||||
import { Conversion, ConversionContext } from "./Conversion"
|
||||
import LayerConfig from "../LayerConfig"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import Translations from "../../../UI/i18n/Translations"
|
||||
|
@ -23,7 +23,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
|||
this._includeClosedNotesDays = includeClosedNotesDays
|
||||
}
|
||||
|
||||
convert(layerJson: LayerConfigJson, context: string): { result: LayerConfigJson } {
|
||||
convert(layerJson: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
const t = Translations.t.importLayer
|
||||
|
||||
/**
|
||||
|
@ -78,7 +78,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
|||
return { ...translation.Subs(subs).translations, _context: translation.context }
|
||||
}
|
||||
|
||||
const result: LayerConfigJson = {
|
||||
return {
|
||||
id: "note_import_" + layer.id,
|
||||
// By disabling the name, the import-layers won't pollute the filter view "name": t.layerName.Subs({title: layer.title.render}).translations,
|
||||
description: trs(t.description, { title: layer.title.render }),
|
||||
|
@ -204,9 +204,5 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
|
|||
},
|
||||
],
|
||||
}
|
||||
|
||||
return {
|
||||
result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Conversion, DesugaringStep } from "./Conversion"
|
||||
import { Conversion, ConversionContext, DesugaringStep } from "./Conversion"
|
||||
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
|
||||
import { Utils } from "../../../Utils"
|
||||
import metapaths from "../../../assets/schemas/layoutconfigmeta.json"
|
||||
|
@ -6,13 +6,11 @@ import tagrenderingmetapaths from "../../../assets/schemas/questionabletagrender
|
|||
import Translations from "../../../UI/i18n/Translations"
|
||||
|
||||
import { parse as parse_html } from "node-html-parser"
|
||||
|
||||
export class ExtractImages extends Conversion<
|
||||
LayoutConfigJson,
|
||||
{ path: string; context: string }[]
|
||||
> {
|
||||
private _isOfficial: boolean
|
||||
private _sharedTagRenderings: Set<string>
|
||||
|
||||
private static readonly layoutMetaPaths = metapaths.filter((mp) => {
|
||||
const typeHint = mp.hints.typehint
|
||||
return (
|
||||
|
@ -25,6 +23,8 @@ export class ExtractImages extends Conversion<
|
|||
)
|
||||
})
|
||||
private static readonly tagRenderingMetaPaths = tagrenderingmetapaths
|
||||
private _isOfficial: boolean
|
||||
private _sharedTagRenderings: Set<string>
|
||||
|
||||
constructor(isOfficial: boolean, sharedTagRenderings: Set<string>) {
|
||||
super("Extract all images from a layoutConfig using the meta paths.", [], "ExctractImages")
|
||||
|
@ -89,11 +89,9 @@ export class ExtractImages extends Conversion<
|
|||
*/
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: { path: string; context: string }[]; errors: string[]; warnings: string[] } {
|
||||
context: ConversionContext
|
||||
): { path: string; context: string }[] {
|
||||
const allFoundImages: { path: string; context: string }[] = []
|
||||
const errors = []
|
||||
const warnings = []
|
||||
for (const metapath of ExtractImages.layoutMetaPaths) {
|
||||
const mightBeTr = ExtractImages.mightBeTagRendering(<any>metapath)
|
||||
const allRenderedValuesAreImages =
|
||||
|
@ -110,7 +108,7 @@ export class ExtractImages extends Conversion<
|
|||
}
|
||||
|
||||
if (foundImage == "") {
|
||||
warnings.push(context + "." + path.join(".") + " Found an empty image")
|
||||
context.warn(context + "." + path.join(".") + " Found an empty image")
|
||||
}
|
||||
|
||||
if (this._sharedTagRenderings?.has(foundImage)) {
|
||||
|
@ -135,17 +133,15 @@ export class ExtractImages extends Conversion<
|
|||
if (allRenderedValuesAreImages && isRendered) {
|
||||
// What we found is an image
|
||||
if (img.leaf === "" || img.leaf["path"] == "") {
|
||||
warnings.push(
|
||||
context +
|
||||
[...path, ...img.path].join(".") +
|
||||
": Found an empty image at "
|
||||
)
|
||||
context
|
||||
.enter(path)
|
||||
.enter(img.path)
|
||||
.warn("Found an emtpy image")
|
||||
} else if (typeof img.leaf !== "string") {
|
||||
;(this._isOfficial ? errors : warnings).push(
|
||||
context +
|
||||
"." +
|
||||
img.path.join(".") +
|
||||
": found an image path that is not a string: " +
|
||||
const c = context.enter(img.path)
|
||||
const w = this._isOfficial ? c.err : c.warn
|
||||
w(
|
||||
"found an image path that is not a string: " +
|
||||
JSON.stringify(img.leaf)
|
||||
)
|
||||
} else {
|
||||
|
@ -176,9 +172,8 @@ export class ExtractImages extends Conversion<
|
|||
} else {
|
||||
for (const foundElement of found) {
|
||||
if (foundElement.leaf === "") {
|
||||
warnings.push(
|
||||
context + "." + foundElement.path.join(".") + " Found an empty image"
|
||||
)
|
||||
context.enter(foundElement.path).warn("Found an empty image")
|
||||
|
||||
continue
|
||||
}
|
||||
if (typeof foundElement.leaf !== "string") {
|
||||
|
@ -215,7 +210,7 @@ export class ExtractImages extends Conversion<
|
|||
}
|
||||
}
|
||||
|
||||
return { result: cleanedImages, errors, warnings }
|
||||
return cleanedImages
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,26 +260,22 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
|
|||
* fixed.layers[0]["mapRendering"][0].icon // => "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg"
|
||||
* fixed.layers[0]["mapRendering"][0].iconBadges[0].then.mappings[0].then // => "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/Something.svg"
|
||||
*/
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; warnings?: string[] } {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(json.id)
|
||||
} catch (e) {
|
||||
// Not a URL, we don't rewrite
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
|
||||
const warnings: string[] = []
|
||||
const absolute = url.protocol + "//" + url.host
|
||||
let relative = url.protocol + "//" + url.host + url.pathname
|
||||
relative = relative.substring(0, relative.lastIndexOf("/"))
|
||||
const self = this
|
||||
|
||||
if (relative.endsWith("assets/generated/themes")) {
|
||||
warnings.push(
|
||||
context.warn(
|
||||
"Detected 'assets/generated/themes' as relative URL. I'm assuming that you are loading your file for the MC-repository, so I'm rewriting all image links as if they were absolute instead of relative"
|
||||
)
|
||||
relative = absolute
|
||||
|
@ -296,7 +287,7 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
|
|||
}
|
||||
|
||||
if (typeof leaf !== "string") {
|
||||
warnings.push(
|
||||
context.warn(
|
||||
"Found a non-string object while replacing images: " + JSON.stringify(leaf)
|
||||
)
|
||||
return leaf
|
||||
|
@ -318,7 +309,7 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
|
|||
continue
|
||||
}
|
||||
const mightBeTr = ExtractImages.mightBeTagRendering(<any>metapath)
|
||||
Utils.WalkPath(metapath.path, json, (leaf, path) => {
|
||||
Utils.WalkPath(metapath.path, json, (leaf) => {
|
||||
if (typeof leaf === "string") {
|
||||
return replaceString(leaf)
|
||||
}
|
||||
|
@ -340,9 +331,6 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
|
|||
})
|
||||
}
|
||||
|
||||
return {
|
||||
warnings,
|
||||
result: json,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { LayoutConfigJson } from "../Json/LayoutConfigJson"
|
|||
import { Utils } from "../../../Utils"
|
||||
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import { DesugaringStep, Each, Fuse, On } from "./Conversion"
|
||||
import { ConversionContext, DesugaringStep, Each, Fuse, On } from "./Conversion"
|
||||
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
|
||||
|
||||
export class UpdateLegacyLayer extends DesugaringStep<
|
||||
|
@ -16,15 +16,12 @@ export class UpdateLegacyLayer extends DesugaringStep<
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: string
|
||||
): { result: LayerConfigJson; errors: string[]; warnings: string[] } {
|
||||
const warnings = []
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
if (typeof json === "string" || json["builtin"] !== undefined) {
|
||||
// Reuse of an already existing layer; return as-is
|
||||
return { result: json, errors: [], warnings: [] }
|
||||
return json
|
||||
}
|
||||
context = context.enter(json.id)
|
||||
let config = { ...json }
|
||||
|
||||
if (config["overpassTags"]) {
|
||||
|
@ -141,7 +138,7 @@ export class UpdateLegacyLayer extends DesugaringStep<
|
|||
}
|
||||
for (const overlay of mapRenderingElement["iconBadges"] ?? []) {
|
||||
if (overlay["badge"] !== true) {
|
||||
warnings.push("Warning: non-overlay element for ", config.id)
|
||||
context.enters("iconBadges", "badge").warn("Non-overlay element")
|
||||
}
|
||||
delete overlay["badge"]
|
||||
}
|
||||
|
@ -229,11 +226,7 @@ export class UpdateLegacyLayer extends DesugaringStep<
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: config,
|
||||
errors: [],
|
||||
warnings,
|
||||
}
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,10 +235,7 @@ class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
super("Small fixes in the theme config", ["roamingRenderings"], "UpdateLegacyTheme")
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const oldThemeConfig = { ...json }
|
||||
|
||||
if (oldThemeConfig.socialImage === "") {
|
||||
|
@ -260,14 +250,8 @@ class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
if (oldThemeConfig["roamingRenderings"].length == 0) {
|
||||
delete oldThemeConfig["roamingRenderings"]
|
||||
} else {
|
||||
return {
|
||||
result: null,
|
||||
errors: [
|
||||
context +
|
||||
": The theme contains roamingRenderings. These are not supported anymore",
|
||||
],
|
||||
warnings: [],
|
||||
}
|
||||
context.err("The theme contains roamingRenderings. These are not supported anymore")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,11 +276,7 @@ class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors: [],
|
||||
warnings: [],
|
||||
result: oldThemeConfig,
|
||||
}
|
||||
return oldThemeConfig
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
Concat,
|
||||
Conversion,
|
||||
ConversionContext,
|
||||
DesugaringContext,
|
||||
DesugaringStep,
|
||||
Each,
|
||||
|
@ -48,20 +49,16 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
|||
return filters
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: string
|
||||
): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
if (json.filter === undefined || json.filter === null) {
|
||||
return { result: json } // Nothing to change here
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
if (json?.filter === undefined || json?.filter === null) {
|
||||
return json // Nothing to change here
|
||||
}
|
||||
|
||||
if (json.filter["sameAs"] !== undefined) {
|
||||
return { result: json } // Nothing to change here
|
||||
return json // Nothing to change here
|
||||
}
|
||||
|
||||
const newFilters: FilterConfigJson[] = []
|
||||
const errors: string[] = []
|
||||
for (const filter of <(FilterConfigJson | string)[]>json.filter) {
|
||||
if (typeof filter !== "string") {
|
||||
newFilters.push(filter)
|
||||
|
@ -71,16 +68,13 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
|||
if (this._state.sharedLayers.size > 0) {
|
||||
const split = filter.split(".")
|
||||
if (split.length > 2) {
|
||||
errors.push(
|
||||
context +
|
||||
": invalid filter name: " +
|
||||
filter +
|
||||
", expected `layername.filterid`"
|
||||
context.err(
|
||||
"invalid filter name: " + filter + ", expected `layername.filterid`"
|
||||
)
|
||||
}
|
||||
const layer = this._state.sharedLayers.get(split[0])
|
||||
if (layer === undefined) {
|
||||
errors.push(context + ": layer '" + split[0] + "' not found")
|
||||
context.err("Layer '" + split[0] + "' not found")
|
||||
}
|
||||
const expectedId = split[1]
|
||||
const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find(
|
||||
|
@ -100,28 +94,28 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
|
|||
Array.from(ExpandFilter.predefinedFilters.keys()),
|
||||
(t) => t
|
||||
)
|
||||
const err =
|
||||
context +
|
||||
".filter: while searching for predifined filter " +
|
||||
filter +
|
||||
": this filter is not found. Perhaps you meant one of: " +
|
||||
suggestions
|
||||
errors.push(err)
|
||||
context
|
||||
.enter(filter)
|
||||
.err(
|
||||
"While searching for predefined filter " +
|
||||
filter +
|
||||
": this filter is not found. Perhaps you meant one of: " +
|
||||
suggestions
|
||||
)
|
||||
}
|
||||
newFilters.push(found)
|
||||
}
|
||||
return {
|
||||
result: {
|
||||
...json,
|
||||
filter: newFilters,
|
||||
},
|
||||
errors,
|
||||
}
|
||||
return { ...json, filter: newFilters }
|
||||
}
|
||||
}
|
||||
|
||||
class ExpandTagRendering extends Conversion<
|
||||
string | TagRenderingConfigJson | { builtin: string | string[]; override: any },
|
||||
| string
|
||||
| TagRenderingConfigJson
|
||||
| {
|
||||
builtin: string | string[]
|
||||
override: any
|
||||
},
|
||||
TagRenderingConfigJson[]
|
||||
> {
|
||||
private readonly _state: DesugaringContext
|
||||
|
@ -137,7 +131,10 @@ class ExpandTagRendering extends Conversion<
|
|||
constructor(
|
||||
state: DesugaringContext,
|
||||
self: LayerConfigJson,
|
||||
options?: { applyCondition?: true | boolean; noHardcodedStrings?: false | boolean }
|
||||
options?: {
|
||||
applyCondition?: true | boolean
|
||||
noHardcodedStrings?: false | boolean
|
||||
}
|
||||
) {
|
||||
super(
|
||||
"Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question",
|
||||
|
@ -160,23 +157,6 @@ class ExpandTagRendering extends Conversion<
|
|||
}
|
||||
}
|
||||
|
||||
convert(
|
||||
json:
|
||||
| string
|
||||
| QuestionableTagRenderingConfigJson
|
||||
| { builtin: string | string[]; override: any },
|
||||
context: string
|
||||
): { result: QuestionableTagRenderingConfigJson[]; errors: string[]; warnings: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
|
||||
return {
|
||||
result: this.convertUntilStable(json, warnings, errors, context),
|
||||
errors,
|
||||
warnings,
|
||||
}
|
||||
}
|
||||
|
||||
private lookup(name: string): TagRenderingConfigJson[] | undefined {
|
||||
const direct = this.directLookup(name)
|
||||
|
||||
|
@ -261,7 +241,13 @@ class ExpandTagRendering extends Conversion<
|
|||
}
|
||||
}
|
||||
|
||||
found = contextWriter.convertStrict(found, layer.id + ".tagRenderings." + found["id"])
|
||||
found = contextWriter.convertStrict(
|
||||
found,
|
||||
ConversionContext.construct(
|
||||
[layer.id, "tagRenderings", found["id"]],
|
||||
["AddContextToTranslations"]
|
||||
)
|
||||
)
|
||||
matchingTrs[i] = found
|
||||
}
|
||||
|
||||
|
@ -271,12 +257,7 @@ class ExpandTagRendering extends Conversion<
|
|||
return undefined
|
||||
}
|
||||
|
||||
private convertOnce(
|
||||
tr: string | any,
|
||||
warnings: string[],
|
||||
errors: string[],
|
||||
ctx: string
|
||||
): TagRenderingConfigJson[] {
|
||||
private convertOnce(tr: string | any, ctx: ConversionContext): TagRenderingConfigJson[] {
|
||||
const state = this._state
|
||||
|
||||
if (typeof tr === "string") {
|
||||
|
@ -285,19 +266,17 @@ class ExpandTagRendering extends Conversion<
|
|||
lookup = this.lookup(tr)
|
||||
}
|
||||
if (lookup === undefined) {
|
||||
const isTagRendering = ctx.indexOf("On(mapRendering") < 0
|
||||
if (isTagRendering && this._state.sharedLayers?.size > 0) {
|
||||
warnings.push(
|
||||
`${ctx}: A literal rendering was detected: ${tr}
|
||||
Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
|
||||
if (this._state.sharedLayers?.size > 0) {
|
||||
ctx.warn(
|
||||
`A literal rendering was detected: ${tr}
|
||||
Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
|
||||
Array.from(state.sharedLayers.keys()).join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
if (this._options?.noHardcodedStrings && this._state?.sharedLayers?.size > 0) {
|
||||
errors.push(
|
||||
ctx +
|
||||
"Detected an invocation to a builtin tagRendering, but this tagrendering was not found: " +
|
||||
ctx.err(
|
||||
"Detected an invocation to a builtin tagRendering, but this tagrendering was not found: " +
|
||||
tr +
|
||||
" \n Did you perhaps forget to add the layer as prefix, such as `icons." +
|
||||
tr +
|
||||
|
@ -334,10 +313,8 @@ class ExpandTagRendering extends Conversion<
|
|||
) {
|
||||
continue
|
||||
}
|
||||
errors.push(
|
||||
"At " +
|
||||
ctx +
|
||||
": an object calling a builtin can only have keys `builtin` or `override`, but a key with name `" +
|
||||
ctx.err(
|
||||
"An object calling a builtin can only have keys `builtin` or `override`, but a key with name `" +
|
||||
key +
|
||||
"` was found. This won't be picked up! The full object is: " +
|
||||
JSON.stringify(tr)
|
||||
|
@ -362,18 +339,16 @@ class ExpandTagRendering extends Conversion<
|
|||
(s) => s
|
||||
)
|
||||
if (state.sharedLayers.size === 0) {
|
||||
warnings.push(
|
||||
ctx +
|
||||
": BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " +
|
||||
ctx.warn(
|
||||
"BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " +
|
||||
name +
|
||||
": layer " +
|
||||
layerName +
|
||||
" not found for now, but ignoring as this is a bootstrapping run. "
|
||||
)
|
||||
} else {
|
||||
errors.push(
|
||||
ctx +
|
||||
": While reusing tagrendering: " +
|
||||
ctx.err(
|
||||
": While reusing tagrendering: " +
|
||||
name +
|
||||
": layer " +
|
||||
layerName +
|
||||
|
@ -388,9 +363,8 @@ class ExpandTagRendering extends Conversion<
|
|||
)
|
||||
}
|
||||
candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i)
|
||||
errors.push(
|
||||
ctx +
|
||||
": The tagRendering with identifier " +
|
||||
ctx.err(
|
||||
"The tagRendering with identifier " +
|
||||
name +
|
||||
" was not found.\n\tDid you mean one of " +
|
||||
candidates.join(", ") +
|
||||
|
@ -413,23 +387,16 @@ class ExpandTagRendering extends Conversion<
|
|||
return [tr]
|
||||
}
|
||||
|
||||
private convertUntilStable(
|
||||
public convert(
|
||||
spec: string | any,
|
||||
warnings: string[],
|
||||
errors: string[],
|
||||
ctx: string
|
||||
ctx: ConversionContext
|
||||
): QuestionableTagRenderingConfigJson[] {
|
||||
const trs = this.convertOnce(spec, warnings, errors, ctx)
|
||||
const trs = this.convertOnce(spec, ctx)
|
||||
|
||||
const result = []
|
||||
for (const tr of trs) {
|
||||
if (typeof tr === "string" || tr["builtin"] !== undefined) {
|
||||
const stable = this.convertUntilStable(
|
||||
tr,
|
||||
warnings,
|
||||
errors,
|
||||
ctx + "(RECURSIVE RESOLVE)"
|
||||
)
|
||||
const stable = this.convert(tr, ctx.inOperation("recursive_resolve"))
|
||||
result.push(...stable)
|
||||
} else {
|
||||
result.push(tr)
|
||||
|
@ -451,15 +418,10 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
|
|||
|
||||
convert(
|
||||
json: QuestionableTagRenderingConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: QuestionableTagRenderingConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
context: ConversionContext
|
||||
): QuestionableTagRenderingConfigJson {
|
||||
if (json.freeform === undefined) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
let spec: Record<string, string>
|
||||
if (typeof json.render === "string") {
|
||||
|
@ -467,40 +429,33 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
|
|||
} else {
|
||||
spec = <Record<string, string>>json.render
|
||||
}
|
||||
const errors: string[] = []
|
||||
for (const key in spec) {
|
||||
if (spec[key].indexOf("<a ") >= 0) {
|
||||
// We have a link element, it probably contains something that needs to be substituted...
|
||||
// Let's play this safe and not inline it
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
const fullSpecification = SpecialVisualizations.constructSpecification(spec[key])
|
||||
if (fullSpecification.length > 1) {
|
||||
// We found a special rendering!
|
||||
if (json.freeform.inline === true) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
": 'inline' is set, but the rendering contains a special visualisation...\n " +
|
||||
context.err(
|
||||
"'inline' is set, but the rendering contains a special visualisation...\n " +
|
||||
spec[key]
|
||||
)
|
||||
}
|
||||
json = JSON.parse(JSON.stringify(json))
|
||||
json.freeform.inline = false
|
||||
return { result: json, errors }
|
||||
return json
|
||||
}
|
||||
}
|
||||
json = JSON.parse(JSON.stringify(json))
|
||||
if (typeof json.freeform === "string") {
|
||||
errors.push("At " + context + ": 'freeform' is a string, but should be an object")
|
||||
return { result: json, errors }
|
||||
context.err("'freeform' is a string, but should be an object")
|
||||
return json
|
||||
}
|
||||
try {
|
||||
json.freeform.inline ??= true
|
||||
} catch (e) {
|
||||
errors.push("At " + context + ": " + e.message)
|
||||
}
|
||||
return { result: json, errors }
|
||||
json.freeform.inline ??= true
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -513,15 +468,12 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: string
|
||||
): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
if (
|
||||
json.tagRenderings === undefined ||
|
||||
json.tagRenderings.some((tr) => tr["id"] === "leftover-questions")
|
||||
) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
json = JSON.parse(JSON.stringify(json))
|
||||
const allSpecials: Exclude<RenderingSpecification, string>[] = []
|
||||
|
@ -537,13 +489,9 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
|||
(sp) => sp.args.length === 0 || sp.args[0].trim() === ""
|
||||
)
|
||||
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
if (noLabels.length > 1) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
": multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
|
||||
context.err(
|
||||
"Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -569,10 +517,8 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
|||
?.map((a) => a.trim())
|
||||
?.filter((s) => s != "")
|
||||
if (blacklisted?.length > 0 && used?.length > 0) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
": the {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
|
||||
context.err(
|
||||
"The {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
|
||||
"\n Whitelisted: " +
|
||||
used.join(", ") +
|
||||
"\n Blacklisted: " +
|
||||
|
@ -581,10 +527,8 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
for (const usedLabel of used) {
|
||||
if (!allLabels.has(usedLabel)) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
": this layers specifies a special question element for label `" +
|
||||
context.err(
|
||||
"This layers specifies a special question element for label `" +
|
||||
usedLabel +
|
||||
"`, but this label doesn't exist.\n" +
|
||||
" Available labels are " +
|
||||
|
@ -607,11 +551,7 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
json.tagRenderings.push(question)
|
||||
}
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,12 +567,9 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
|||
this._desugaring = desugaring
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: string
|
||||
): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
if (this._desugaring.tagRenderings === null) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
json = JSON.parse(JSON.stringify(json))
|
||||
|
||||
|
@ -693,7 +630,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
|
|||
json.tagRenderings?.push(trc)
|
||||
}
|
||||
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -798,21 +735,16 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
|
|||
* ]
|
||||
* new ExpandRewrite().convertStrict(spec, "test") // => expected
|
||||
*/
|
||||
convert(
|
||||
json: T | RewritableConfigJson<T>,
|
||||
context: string
|
||||
): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(json: T | RewritableConfigJson<T>, context: ConversionContext): T[] {
|
||||
if (json === null || json === undefined) {
|
||||
return { result: [] }
|
||||
return []
|
||||
}
|
||||
|
||||
if (json["rewrite"] === undefined) {
|
||||
// not a rewrite
|
||||
return { result: [<T>json] }
|
||||
return [<T>json]
|
||||
}
|
||||
|
||||
console.log("Rewriting at", context)
|
||||
|
||||
const rewrite = <RewritableConfigJson<T>>json
|
||||
const keysToRewrite = rewrite.rewrite
|
||||
const ts: T[] = []
|
||||
|
@ -824,7 +756,9 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
|
|||
for (let j = i + 1; j < keysToRewrite.sourceString.length; j++) {
|
||||
const toRewrite = keysToRewrite.sourceString[j]
|
||||
if (toRewrite.indexOf(guard) >= 0) {
|
||||
throw `${context} Error in rewrite: sourcestring[${i}] is a substring of sourcestring[${j}]: ${guard} will be substituted away before ${toRewrite} is reached.`
|
||||
context.err(
|
||||
`sourcestring[${i}] is a substring of sourcestring[${j}]: ${guard} will be substituted away before ${toRewrite} is reached.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -835,7 +769,11 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
|
|||
for (let i = 0; i < rewrite.rewrite.into.length; i++) {
|
||||
const into = keysToRewrite.into[i]
|
||||
if (into.length !== rewrite.rewrite.sourceString.length) {
|
||||
throw `${context}.into.${i} Error in rewrite: there are ${rewrite.rewrite.sourceString.length} keys to rewrite, but entry ${i} has only ${into.length} values`
|
||||
context
|
||||
.enters("into", i)
|
||||
.err(
|
||||
`Error in rewrite: there are ${rewrite.rewrite.sourceString.length} keys to rewrite, but entry ${i} has only ${into.length} values`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -850,7 +788,7 @@ export class ExpandRewrite<T> extends Conversion<T | RewritableConfigJson<T>, T[
|
|||
ts.push(t)
|
||||
}
|
||||
|
||||
return { result: ts }
|
||||
return ts
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -925,7 +863,13 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
|||
* errors // => []
|
||||
*/
|
||||
private static convertIfNeeded(
|
||||
input: (object & { special: { type: string } }) | any,
|
||||
input:
|
||||
| (object & {
|
||||
special: {
|
||||
type: string
|
||||
}
|
||||
})
|
||||
| any,
|
||||
errors: string[],
|
||||
context: string
|
||||
): any {
|
||||
|
@ -1090,15 +1034,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
|||
* const expected = {render: {'en': "{image_carousel(image)}Some footer"}}
|
||||
* result // => expected
|
||||
*/
|
||||
convert(
|
||||
json: TagRenderingConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: TagRenderingConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
|
||||
const errors = []
|
||||
json = Utils.Clone(json)
|
||||
const paths: ConfigMeta[] = tagrenderingconfigmeta
|
||||
|
@ -1111,10 +1047,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1126,51 +1059,42 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
|
|||
this._expand = new ExpandTagRendering(state, layer)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: PointRenderingConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: PointRenderingConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: PointRenderingConfigJson, context: ConversionContext): PointRenderingConfigJson {
|
||||
if (!json["iconBadges"]) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
const badgesJson = json.iconBadges
|
||||
|
||||
const iconBadges: { if: TagConfigJson; then: string | TagRenderingConfigJson }[] = []
|
||||
const iconBadges: {
|
||||
if: TagConfigJson
|
||||
then: string | TagRenderingConfigJson
|
||||
}[] = []
|
||||
|
||||
const errs: string[] = []
|
||||
const warns: string[] = []
|
||||
for (let i = 0; i < badgesJson.length; i++) {
|
||||
const iconBadge: { if: TagConfigJson; then: string | TagRenderingConfigJson } =
|
||||
badgesJson[i]
|
||||
const { errors, result, warnings } = this._expand.convert(
|
||||
const iconBadge: {
|
||||
if: TagConfigJson
|
||||
then: string | TagRenderingConfigJson
|
||||
} = badgesJson[i]
|
||||
const expanded = this._expand.convert(
|
||||
<QuestionableTagRenderingConfigJson>iconBadge.then,
|
||||
context + ".iconBadges[" + i + "]"
|
||||
context.enters("iconBadges", i)
|
||||
)
|
||||
errs.push(...errors)
|
||||
warns.push(...warnings)
|
||||
if (result === undefined) {
|
||||
if (expanded === undefined) {
|
||||
iconBadges.push(iconBadge)
|
||||
continue
|
||||
}
|
||||
|
||||
iconBadges.push(
|
||||
...result.map((resolved) => ({
|
||||
...expanded.map((resolved) => ({
|
||||
if: iconBadge.if,
|
||||
then: resolved,
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
result: { ...json, iconBadges },
|
||||
errors: errs,
|
||||
warnings: warns,
|
||||
}
|
||||
return { ...json, iconBadges }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1196,15 +1120,7 @@ class SetFullNodeDatabase extends DesugaringStep<LayerConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: LayerConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
const needsSpecial =
|
||||
json.tagRenderings?.some((tr) => {
|
||||
if (typeof tr === "string") {
|
||||
|
@ -1214,12 +1130,10 @@ class SetFullNodeDatabase extends DesugaringStep<LayerConfigJson> {
|
|||
return specs?.some((sp) => sp.needsNodeDatabase)
|
||||
}) ?? false
|
||||
if (!needsSpecial) {
|
||||
return { result: json }
|
||||
}
|
||||
return {
|
||||
result: { ...json, fullNodeDatabase: true },
|
||||
information: ["Layer " + json.id + " needs the fullNodeDatabase"],
|
||||
return json
|
||||
}
|
||||
context.info("Layer " + json.id + " needs the fullNodeDatabase")
|
||||
return { ...json, fullNodeDatabase: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1235,9 +1149,9 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
|
|||
this._state = state
|
||||
}
|
||||
|
||||
convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } {
|
||||
convert(layerConfig: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
if (!layerConfig.tagRenderings || layerConfig.source === "special") {
|
||||
return { result: layerConfig }
|
||||
return layerConfig
|
||||
}
|
||||
const state = this._state
|
||||
const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap")
|
||||
|
@ -1254,9 +1168,7 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: layerConfig,
|
||||
}
|
||||
return layerConfig
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1274,30 +1186,22 @@ class ExpandMarkerRenderings extends DesugaringStep<IconConfigJson> {
|
|||
this._state = state
|
||||
}
|
||||
|
||||
convert(
|
||||
json: IconConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: IconConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: IconConfigJson, context: ConversionContext): IconConfigJson {
|
||||
const expander = new ExpandTagRendering(this._state, this._layer)
|
||||
const result: IconConfigJson = { icon: undefined, color: undefined }
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
if (json.icon && json.icon["builtin"]) {
|
||||
result.icon = expander.convertJoin(<any>json.icon, context, errors, warnings)[0]
|
||||
result.icon = expander.convert(<any>json.icon, context.enter("icon"))[0]
|
||||
} else {
|
||||
result.icon = json.icon
|
||||
}
|
||||
if (json.color && json.color["builtin"]) {
|
||||
result.color = expander.convertJoin(<any>json.color, context, errors, warnings)[0]
|
||||
result.color = expander.convert(<any>json.color, context.enter("color"))[0]
|
||||
} else {
|
||||
result.color = json.color
|
||||
}
|
||||
return { result, errors, warnings }
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
Concat,
|
||||
Conversion,
|
||||
ConversionContext,
|
||||
DesugaringContext,
|
||||
DesugaringStep,
|
||||
Each,
|
||||
|
@ -33,12 +34,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
this._state = state
|
||||
}
|
||||
|
||||
convert(
|
||||
json: string | LayerConfigJson,
|
||||
context: string
|
||||
): { result: LayerConfigJson[]; errors: string[]; information?: string[] } {
|
||||
const errors = []
|
||||
const information = []
|
||||
convert(json: string | LayerConfigJson, context: ConversionContext): LayerConfigJson[] {
|
||||
const state = this._state
|
||||
|
||||
function reportNotFound(name: string) {
|
||||
|
@ -50,7 +46,7 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
withDistance.sort((a, b) => a[1] - b[1])
|
||||
const ids = withDistance.map((n) => n[0])
|
||||
// Known builtin layers are "+.join(",")+"\n For more information, see "
|
||||
errors.push(`${context}: The layer with name ${name} was not found as a builtin layer. Perhaps you meant ${ids[0]}, ${ids[1]} or ${ids[2]}?
|
||||
context.err(`The layer with name ${name} was not found as a builtin layer. Perhaps you meant ${ids[0]}, ${ids[1]} or ${ids[2]}?
|
||||
For an overview of all available layers, refer to https://github.com/pietervdvn/MapComplete/blob/develop/Docs/BuiltinLayers.md`)
|
||||
}
|
||||
|
||||
|
@ -58,119 +54,101 @@ class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJs
|
|||
const found = state.sharedLayers.get(json)
|
||||
if (found === undefined) {
|
||||
reportNotFound(json)
|
||||
return {
|
||||
result: null,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: [found],
|
||||
errors,
|
||||
return null
|
||||
}
|
||||
return [found]
|
||||
}
|
||||
|
||||
if (json["builtin"] !== undefined) {
|
||||
let names = json["builtin"]
|
||||
if (typeof names === "string") {
|
||||
names = [names]
|
||||
if (json["builtin"] === undefined) {
|
||||
return [json]
|
||||
}
|
||||
|
||||
let names = json["builtin"]
|
||||
if (typeof names === "string") {
|
||||
names = [names]
|
||||
}
|
||||
const layers = []
|
||||
|
||||
for (const name of names) {
|
||||
const found = Utils.Clone(state.sharedLayers.get(name))
|
||||
if (found === undefined) {
|
||||
reportNotFound(name)
|
||||
continue
|
||||
}
|
||||
if (
|
||||
json["override"]["tagRenderings"] !== undefined &&
|
||||
(found["tagRenderings"] ?? []).length > 0
|
||||
) {
|
||||
context.err(
|
||||
`When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
|
||||
)
|
||||
}
|
||||
try {
|
||||
Utils.Merge(json["override"], found)
|
||||
layers.push(found)
|
||||
} catch (e) {
|
||||
context.err(
|
||||
`Could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
|
||||
json["override"]
|
||||
)}`
|
||||
)
|
||||
}
|
||||
const layers = []
|
||||
|
||||
for (const name of names) {
|
||||
const found = Utils.Clone(state.sharedLayers.get(name))
|
||||
if (found === undefined) {
|
||||
reportNotFound(name)
|
||||
continue
|
||||
}
|
||||
if (
|
||||
json["override"]["tagRenderings"] !== undefined &&
|
||||
(found["tagRenderings"] ?? []).length > 0
|
||||
) {
|
||||
errors.push(
|
||||
`At ${context}: when overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
|
||||
)
|
||||
}
|
||||
try {
|
||||
Utils.Merge(json["override"], found)
|
||||
layers.push(found)
|
||||
} catch (e) {
|
||||
errors.push(
|
||||
`At ${context}: could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
|
||||
json["override"]
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
if (json["hideTagRenderingsWithLabels"]) {
|
||||
const hideLabels: Set<string> = new Set(json["hideTagRenderingsWithLabels"])
|
||||
// These labels caused at least one deletion
|
||||
const usedLabels: Set<string> = new Set<string>()
|
||||
const filtered = []
|
||||
for (const tr of found.tagRenderings) {
|
||||
const labels = tr["labels"]
|
||||
if (labels !== undefined) {
|
||||
const forbiddenLabel = labels.findIndex((l) => hideLabels.has(l))
|
||||
if (forbiddenLabel >= 0) {
|
||||
usedLabels.add(labels[forbiddenLabel])
|
||||
information.push(
|
||||
context +
|
||||
": Dropping tagRendering " +
|
||||
tr["id"] +
|
||||
" as it has a forbidden label: " +
|
||||
labels[forbiddenLabel]
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (hideLabels.has(tr["id"])) {
|
||||
usedLabels.add(tr["id"])
|
||||
information.push(
|
||||
context +
|
||||
": Dropping tagRendering " +
|
||||
if (json["hideTagRenderingsWithLabels"]) {
|
||||
const hideLabels: Set<string> = new Set(json["hideTagRenderingsWithLabels"])
|
||||
// These labels caused at least one deletion
|
||||
const usedLabels: Set<string> = new Set<string>()
|
||||
const filtered = []
|
||||
for (const tr of found.tagRenderings) {
|
||||
const labels = tr["labels"]
|
||||
if (labels !== undefined) {
|
||||
const forbiddenLabel = labels.findIndex((l) => hideLabels.has(l))
|
||||
if (forbiddenLabel >= 0) {
|
||||
usedLabels.add(labels[forbiddenLabel])
|
||||
context.info(
|
||||
"Dropping tagRendering " +
|
||||
tr["id"] +
|
||||
" as its id is a forbidden label"
|
||||
" as it has a forbidden label: " +
|
||||
labels[forbiddenLabel]
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (hideLabels.has(tr["group"])) {
|
||||
usedLabels.add(tr["group"])
|
||||
information.push(
|
||||
context +
|
||||
": Dropping tagRendering " +
|
||||
tr["id"] +
|
||||
" as its group `" +
|
||||
tr["group"] +
|
||||
"` is a forbidden label"
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
filtered.push(tr)
|
||||
}
|
||||
const unused = Array.from(hideLabels).filter((l) => !usedLabels.has(l))
|
||||
if (unused.length > 0) {
|
||||
errors.push(
|
||||
"This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
|
||||
unused.join(", ") +
|
||||
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
|
||||
|
||||
if (hideLabels.has(tr["id"])) {
|
||||
usedLabels.add(tr["id"])
|
||||
context.info(
|
||||
"Dropping tagRendering " + tr["id"] + " as its id is a forbidden label"
|
||||
)
|
||||
continue
|
||||
}
|
||||
found.tagRenderings = filtered
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: layers,
|
||||
errors,
|
||||
information,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: [json],
|
||||
errors,
|
||||
if (hideLabels.has(tr["group"])) {
|
||||
usedLabels.add(tr["group"])
|
||||
context.info(
|
||||
"Dropping tagRendering " +
|
||||
tr["id"] +
|
||||
" as its group `" +
|
||||
tr["group"] +
|
||||
"` is a forbidden label"
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
filtered.push(tr)
|
||||
}
|
||||
const unused = Array.from(hideLabels).filter((l) => !usedLabels.has(l))
|
||||
if (unused.length > 0) {
|
||||
context.err(
|
||||
"This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
|
||||
unused.join(", ") +
|
||||
"\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
|
||||
)
|
||||
}
|
||||
found.tagRenderings = filtered
|
||||
}
|
||||
}
|
||||
return layers
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,12 +164,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
this._state = state
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const state = this._state
|
||||
json.layers = [...json.layers]
|
||||
const alreadyLoaded = new Set(json.layers.map((l) => l["id"]))
|
||||
|
@ -199,11 +172,11 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
for (const layerName of Constants.added_by_default) {
|
||||
const v = state.sharedLayers.get(layerName)
|
||||
if (v === undefined) {
|
||||
errors.push("Default layer " + layerName + " not found")
|
||||
context.err("Default layer " + layerName + " not found")
|
||||
continue
|
||||
}
|
||||
if (alreadyLoaded.has(v.id)) {
|
||||
warnings.push(
|
||||
context.warn(
|
||||
"Layout " +
|
||||
context +
|
||||
" already has a layer with name " +
|
||||
|
@ -215,11 +188,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
json.layers.push(v)
|
||||
}
|
||||
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,21 +201,13 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
if (!(json.enableNoteImports ?? true)) {
|
||||
return {
|
||||
warnings: [
|
||||
"Not creating a note import layers for theme " +
|
||||
json.id +
|
||||
" as they are disabled",
|
||||
],
|
||||
result: json,
|
||||
}
|
||||
context.info(
|
||||
"Not creating a note import layers for theme " + json.id + " as they are disabled"
|
||||
)
|
||||
return json
|
||||
}
|
||||
const errors = []
|
||||
|
||||
json = { ...json }
|
||||
const allLayers: LayerConfigJson[] = <LayerConfigJson[]>json.layers
|
||||
|
@ -278,20 +239,17 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
try {
|
||||
const importLayerResult = creator.convert(
|
||||
layer,
|
||||
context + ".(noteimportlayer)[" + i1 + "]"
|
||||
context.inOperation(this.name).enter(i1)
|
||||
)
|
||||
if (importLayerResult.result !== undefined) {
|
||||
json.layers.push(importLayerResult.result)
|
||||
if (importLayerResult !== undefined) {
|
||||
json.layers.push(importLayerResult)
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push("Could not generate an import-layer for " + layer.id + " due to " + e)
|
||||
context.err("Could not generate an import-layer for " + layer.id + " due to " + e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
result: json,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,17 +262,9 @@ class AddContextToTranslationsInLayout extends DesugaringStep<LayoutConfigJson>
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: LayoutConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const conversion = new AddContextToTranslations<LayoutConfigJson>("themes:")
|
||||
return conversion.convert(json, json.id)
|
||||
return conversion.convert(json, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,13 +277,10 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const overrideAll = json.overrideAll
|
||||
if (overrideAll === undefined) {
|
||||
return { result: json, warnings: [], errors: [] }
|
||||
return json
|
||||
}
|
||||
|
||||
json = { ...json }
|
||||
|
@ -346,8 +293,7 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
|
|||
newLayers.push(layer)
|
||||
}
|
||||
json.layers = newLayers
|
||||
|
||||
return { result: json, warnings: [], errors: [] }
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,18 +404,14 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
return dependenciesToAdd
|
||||
}
|
||||
|
||||
convert(
|
||||
theme: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; information: string[] } {
|
||||
convert(theme: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const state = this._state
|
||||
const allKnownLayers: Map<string, LayerConfigJson> = state.sharedLayers
|
||||
const knownTagRenderings: Map<string, TagRenderingConfigJson> = state.tagRenderings
|
||||
const information = []
|
||||
const layers: LayerConfigJson[] = <LayerConfigJson[]>theme.layers // Layers should be expanded at this point
|
||||
|
||||
knownTagRenderings.forEach((value, key) => {
|
||||
value.id = key
|
||||
value["id"] = key
|
||||
})
|
||||
|
||||
const dependencies = AddDependencyLayersToTheme.CalculateDependencies(
|
||||
|
@ -481,23 +423,16 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
}
|
||||
if (dependencies.length > 0) {
|
||||
for (const dependency of dependencies) {
|
||||
information.push(
|
||||
context +
|
||||
": added " +
|
||||
dependency.config.id +
|
||||
" to the theme. " +
|
||||
dependency.reason
|
||||
context.info(
|
||||
"Added " + dependency.config.id + " to the theme. " + dependency.reason
|
||||
)
|
||||
}
|
||||
}
|
||||
layers.unshift(...dependencies.map((l) => l.config))
|
||||
|
||||
return {
|
||||
result: {
|
||||
...theme,
|
||||
layers: layers,
|
||||
},
|
||||
information,
|
||||
...theme,
|
||||
layers: layers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -510,17 +445,9 @@ class PreparePersonalTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
this._state = state
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: LayoutConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
if (json.id !== "personal") {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
|
||||
// The only thing this _really_ does, is adding the layer-ids into 'layers'
|
||||
|
@ -529,10 +456,8 @@ class PreparePersonalTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
json.layers = Array.from(this._state.sharedLayers.keys())
|
||||
.filter((l) => this._state.sharedLayers.get(l).source !== null)
|
||||
.filter((l) => this._state.publicLayers.has(l))
|
||||
return {
|
||||
result: json,
|
||||
information: ["The personal theme has " + json.layers.length + " public layers"],
|
||||
}
|
||||
context.info("The personal theme has " + json.layers.length + " public layers")
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -545,19 +470,10 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: LayoutConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
if (json.hideFromOverview === true) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
const warnings = []
|
||||
for (const layer of json.layers) {
|
||||
if (typeof layer === "string") {
|
||||
continue
|
||||
|
@ -570,18 +486,15 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep<LayoutConfigJson>
|
|||
continue
|
||||
}
|
||||
|
||||
const wrn =
|
||||
context.warn(
|
||||
"The theme " +
|
||||
json.id +
|
||||
" has an inline layer: " +
|
||||
layer["id"] +
|
||||
". This is discouraged."
|
||||
warnings.push(wrn)
|
||||
}
|
||||
return {
|
||||
result: json,
|
||||
warnings,
|
||||
json.id +
|
||||
" has an inline layer: " +
|
||||
layer["id"] +
|
||||
". This is discouraged."
|
||||
)
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -616,29 +529,25 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
|
|||
this.state = state
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const result = super.convert(json, context)
|
||||
if (this.state.publicLayers.size === 0) {
|
||||
// THis is a bootstrapping run, no need to already set this flag
|
||||
return result
|
||||
}
|
||||
|
||||
const needsNodeDatabase = result.result.layers?.some((l: LayerConfigJson) =>
|
||||
l.tagRenderings?.some((tr: TagRenderingConfigJson) =>
|
||||
ValidationUtils.getSpecialVisualisations(tr)?.some(
|
||||
const needsNodeDatabase = result.layers?.some((l: LayerConfigJson) =>
|
||||
l.tagRenderings?.some((tr) =>
|
||||
ValidationUtils.getSpecialVisualisations(<any>tr)?.some(
|
||||
(special) => special.needsNodeDatabase
|
||||
)
|
||||
)
|
||||
)
|
||||
if (needsNodeDatabase) {
|
||||
result.information.push(
|
||||
context +
|
||||
": setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
|
||||
context.info(
|
||||
"Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
|
||||
)
|
||||
result.result.enableNodeDatabase = true
|
||||
result.enableNodeDatabase = true
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DesugaringStep, Each, Fuse, On } from "./Conversion"
|
||||
import { ConversionContext, DesugaringStep, Each, Fuse, On } from "./Conversion"
|
||||
import { LayerConfigJson } from "../Json/LayerConfigJson"
|
||||
import LayerConfig from "../LayerConfig"
|
||||
import { Utils } from "../../../Utils"
|
||||
|
@ -33,12 +33,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
|
|||
this._languages = languages ?? ["en"]
|
||||
}
|
||||
|
||||
convert(
|
||||
obj: any,
|
||||
context: string
|
||||
): { result: LayerConfig; errors: string[]; warnings: string[] } {
|
||||
const errors = []
|
||||
const warnings: string[] = []
|
||||
convert(obj: any, context: ConversionContext): LayerConfig {
|
||||
const translations = Translation.ExtractAllTranslationsFrom(obj)
|
||||
for (const neededLanguage of this._languages) {
|
||||
translations
|
||||
|
@ -48,23 +43,20 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
|
|||
t.tr.translations["*"] === undefined
|
||||
)
|
||||
.forEach((missing) => {
|
||||
errors.push(
|
||||
context +
|
||||
"A theme should be translation-complete for " +
|
||||
neededLanguage +
|
||||
", but it lacks a translation for " +
|
||||
missing.context +
|
||||
".\n\tThe known translation is " +
|
||||
missing.tr.textFor("en")
|
||||
)
|
||||
context
|
||||
.enter(missing.context.split("."))
|
||||
.err(
|
||||
`The theme ${obj.id} should be translation-complete for ` +
|
||||
neededLanguage +
|
||||
", but it lacks a translation for " +
|
||||
missing.context +
|
||||
".\n\tThe known translation is " +
|
||||
missing.tr.textFor("en")
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
result: obj,
|
||||
errors,
|
||||
warnings,
|
||||
}
|
||||
return obj
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,58 +76,47 @@ export class DoesImageExist extends DesugaringStep<string> {
|
|||
this.doesPathExist = checkExistsSync
|
||||
}
|
||||
|
||||
convert(
|
||||
image: string,
|
||||
context: string
|
||||
): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
|
||||
convert(image: string, context: ConversionContext): string {
|
||||
if (this._ignore?.has(image)) {
|
||||
return { result: image }
|
||||
return image
|
||||
}
|
||||
|
||||
const errors = []
|
||||
const warnings = []
|
||||
const information = []
|
||||
if (image.indexOf("{") >= 0) {
|
||||
information.push("Ignoring image with { in the path: " + image)
|
||||
return { result: image }
|
||||
context.info("Ignoring image with { in the path: " + image)
|
||||
return image
|
||||
}
|
||||
|
||||
if (image === "assets/SocialImage.png") {
|
||||
return { result: image }
|
||||
return image
|
||||
}
|
||||
if (image.match(/[a-z]*/)) {
|
||||
if (Svg.All[image + ".svg"] !== undefined) {
|
||||
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
|
||||
return { result: image }
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
if (image.startsWith("<") && image.endsWith(">")) {
|
||||
// This is probably HTML, you're on your own here
|
||||
return { result: image }
|
||||
return image
|
||||
}
|
||||
|
||||
if (!this._knownImagePaths.has(image)) {
|
||||
if (this.doesPathExist === undefined) {
|
||||
errors.push(
|
||||
context.err(
|
||||
`Image with path ${image} not found or not attributed; it is used in ${context}`
|
||||
)
|
||||
} else if (!this.doesPathExist(image)) {
|
||||
errors.push(
|
||||
context.err(
|
||||
`Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.`
|
||||
)
|
||||
} else {
|
||||
errors.push(
|
||||
context.err(
|
||||
`Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`
|
||||
)
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: image,
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
}
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,28 +146,20 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
}
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } {
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
const information: string[] = []
|
||||
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const theme = new LayoutConfig(json, this._isBuiltin)
|
||||
|
||||
{
|
||||
// Legacy format checks
|
||||
if (this._isBuiltin) {
|
||||
if (json["units"] !== undefined) {
|
||||
errors.push(
|
||||
context.err(
|
||||
"The theme " +
|
||||
json.id +
|
||||
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
|
||||
)
|
||||
}
|
||||
if (json["roamingRenderings"] !== undefined) {
|
||||
errors.push(
|
||||
context.err(
|
||||
"Theme " +
|
||||
json.id +
|
||||
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
|
||||
|
@ -196,10 +169,10 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
}
|
||||
if (this._isBuiltin && this._extractImages !== undefined) {
|
||||
// Check images: are they local, are the licenses there, is the theme icon square, ...
|
||||
const images = this._extractImages.convertStrict(json, "validation")
|
||||
const images = this._extractImages.convert(json, context.inOperation("ValidateTheme"))
|
||||
const remoteImages = images.filter((img) => img.path.indexOf("http") == 0)
|
||||
for (const remoteImage of remoteImages) {
|
||||
errors.push(
|
||||
context.err(
|
||||
"Found a remote image: " +
|
||||
remoteImage +
|
||||
" in theme " +
|
||||
|
@ -208,20 +181,14 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
)
|
||||
}
|
||||
for (const image of images) {
|
||||
this._validateImage.convertJoin(
|
||||
image.path,
|
||||
context === undefined ? "" : ` in the theme ${context} at ${image.context}`,
|
||||
errors,
|
||||
warnings,
|
||||
information
|
||||
)
|
||||
this._validateImage.convert(image.path, context.enters(image.context))
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (this._isBuiltin) {
|
||||
if (theme.id !== theme.id.toLowerCase()) {
|
||||
errors.push("Theme ids should be in lowercase, but it is " + theme.id)
|
||||
context.err("Theme ids should be in lowercase, but it is " + theme.id)
|
||||
}
|
||||
|
||||
const filename = this._path.substring(
|
||||
|
@ -229,7 +196,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
this._path.length - 5
|
||||
)
|
||||
if (theme.id !== filename) {
|
||||
errors.push(
|
||||
context.err(
|
||||
"Theme ids should be the same as the name.json, but we got id: " +
|
||||
theme.id +
|
||||
" and filename " +
|
||||
|
@ -239,54 +206,41 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
")"
|
||||
)
|
||||
}
|
||||
this._validateImage.convertJoin(
|
||||
theme.icon,
|
||||
context + ".icon",
|
||||
errors,
|
||||
warnings,
|
||||
information
|
||||
)
|
||||
this._validateImage.convert(theme.icon, context.enter("icon"))
|
||||
}
|
||||
const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"]))
|
||||
if (dups.length > 0) {
|
||||
errors.push(
|
||||
context.err(
|
||||
`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`
|
||||
)
|
||||
}
|
||||
if (json["mustHaveLanguage"] !== undefined) {
|
||||
const checked = new ValidateLanguageCompleteness(
|
||||
...json["mustHaveLanguage"]
|
||||
).convert(theme, theme.id)
|
||||
|
||||
errors.push(...checked.errors)
|
||||
new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert(
|
||||
theme,
|
||||
context
|
||||
)
|
||||
}
|
||||
if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) {
|
||||
// The first key in the the title-field must be english, otherwise the title in the loading page will be the different language
|
||||
const targetLanguage = theme.title.SupportedLanguages()[0]
|
||||
if (targetLanguage !== "en") {
|
||||
warnings.push(
|
||||
context.err(
|
||||
`TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`
|
||||
)
|
||||
}
|
||||
|
||||
// Official, public themes must have a full english translation
|
||||
const checked = new ValidateLanguageCompleteness("en").convert(theme, theme.id)
|
||||
errors.push(...checked.errors)
|
||||
new ValidateLanguageCompleteness("en").convert(theme, context)
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push(e)
|
||||
context.err(e)
|
||||
}
|
||||
|
||||
if (theme.id !== "personal") {
|
||||
new DetectDuplicatePresets().convertJoin(theme, context, errors, warnings, information)
|
||||
new DetectDuplicatePresets().convert(theme, context)
|
||||
}
|
||||
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,16 +268,12 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
_: string
|
||||
): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
const overrideAll = json.overrideAll
|
||||
if (overrideAll === undefined) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
|
||||
const errors = []
|
||||
const withOverride = json.layers.filter((l) => l["override"] !== undefined)
|
||||
|
||||
for (const layer of withOverride) {
|
||||
|
@ -342,12 +292,12 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
|
|||
" has a shadowed property: " +
|
||||
key +
|
||||
" is overriden by overrideAll of the theme"
|
||||
errors.push(w)
|
||||
context.err(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { result: json, errors }
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,28 +306,14 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> {
|
|||
super("Miscelleanous checks on the theme", [], "MiscThemesChecks")
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayoutConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: LayoutConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
const warnings = []
|
||||
const errors = []
|
||||
convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
|
||||
if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) {
|
||||
errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")")
|
||||
context.err("The theme " + json.id + " has no 'layers' defined")
|
||||
}
|
||||
if (json.socialImage === "") {
|
||||
warnings.push("Social image for theme " + json.id + " is the emtpy string")
|
||||
}
|
||||
return {
|
||||
result: json,
|
||||
warnings,
|
||||
errors,
|
||||
context.warn("Social image for theme " + json.id + " is the emtpy string")
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,17 +336,9 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
|
|||
)
|
||||
}
|
||||
|
||||
convert(
|
||||
json: TagRenderingConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: TagRenderingConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
|
||||
if (!(json.mappings?.length > 0)) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
|
||||
const tagRendering = new TagRenderingConfig(json)
|
||||
|
@ -438,10 +366,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,14 +429,9 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
|
|||
* r.errors.length // => 1
|
||||
* r.errors[0].indexOf("The mapping key=value&x=y is fully matched by a previous mapping (namely 0)") >= 0 // => true
|
||||
*/
|
||||
convert(
|
||||
json: TagRenderingConfigJson,
|
||||
context: string
|
||||
): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
|
||||
if (json.mappings === undefined || json.mappings.length === 0) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
const defaultProperties = {}
|
||||
for (const calculatedTagName of this._calculatedTagNames) {
|
||||
|
@ -547,12 +467,12 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
|
|||
json.mappings[j]["hideInAnswer"] === true &&
|
||||
json.mappings[i]["hideInAnswer"] !== true
|
||||
) {
|
||||
warnings.push(
|
||||
`At ${context}: Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`
|
||||
context.warn(
|
||||
`Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`
|
||||
)
|
||||
} else if (doesMatch) {
|
||||
// The current mapping is shadowed!
|
||||
errors.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
|
||||
context.err(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
|
||||
The mapping ${parsedConditions[i].asHumanString(
|
||||
false,
|
||||
false,
|
||||
|
@ -573,11 +493,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
warnings,
|
||||
result: json,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -613,56 +529,40 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
|
|||
* r.errors.length > 0 // => true
|
||||
* r.errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0) // => true
|
||||
*/
|
||||
convert(
|
||||
json: TagRenderingConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: TagRenderingConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
const information: string[] = []
|
||||
convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
|
||||
if (json.mappings === undefined || json.mappings.length === 0) {
|
||||
return { result: json }
|
||||
return json
|
||||
}
|
||||
const ignoreToken = "ignore-image-in-then"
|
||||
for (let i = 0; i < json.mappings.length; i++) {
|
||||
const mapping = json.mappings[i]
|
||||
const ignore = mapping["#"]?.indexOf(ignoreToken) >= 0
|
||||
const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? [])
|
||||
const ctx = `${context}.mappings[${i}]`
|
||||
const ctx = context.enters("mappings", i)
|
||||
if (images.length > 0) {
|
||||
if (!ignore) {
|
||||
errors.push(
|
||||
`${ctx}: A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": <your-image>\` instead. The images found are ${images.join(
|
||||
ctx.err(
|
||||
`A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": <your-image>\` instead. The images found are ${images.join(
|
||||
", "
|
||||
)}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`
|
||||
)
|
||||
} else {
|
||||
information.push(
|
||||
`${ctx}: Ignored image ${images.join(
|
||||
ctx.info(
|
||||
`Ignored image ${images.join(
|
||||
", "
|
||||
)} in 'then'-clause of a mapping as this check has been disabled`
|
||||
)
|
||||
|
||||
for (const image of images) {
|
||||
this._doesImageExist.convertJoin(image, ctx, errors, warnings, information)
|
||||
this._doesImageExist.convert(image, ctx)
|
||||
}
|
||||
}
|
||||
} else if (ignore) {
|
||||
warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`)
|
||||
ctx.warn(`Unused '${ignoreToken}' - please remove this`)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
result: json,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -701,20 +601,12 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin
|
|||
|
||||
convert(
|
||||
json: string | Record<string, string>,
|
||||
context: string
|
||||
): {
|
||||
result: string | Record<string, string>
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
const errors = []
|
||||
context: ConversionContext
|
||||
): string | Record<string, string> {
|
||||
if (typeof json === "string") {
|
||||
if (this.isTabnabbingProne(json)) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
": the string " +
|
||||
context.err(
|
||||
"The string " +
|
||||
json +
|
||||
" has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping"
|
||||
)
|
||||
|
@ -722,16 +614,13 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin
|
|||
} else {
|
||||
for (const k in json) {
|
||||
if (this.isTabnabbingProne(json[k])) {
|
||||
errors.push(
|
||||
`At ${context}: the translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`
|
||||
context.err(
|
||||
`The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
errors,
|
||||
result: json,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -745,50 +634,31 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
|
|||
|
||||
convert(
|
||||
json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: TagRenderingConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
const warnings = []
|
||||
const errors = []
|
||||
context: ConversionContext
|
||||
): TagRenderingConfigJson {
|
||||
if (json["special"] !== undefined) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
|
||||
context.err(
|
||||
'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
|
||||
)
|
||||
}
|
||||
if (json["group"]) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
': groups are deprecated, use `"label": ["' +
|
||||
json["group"] +
|
||||
'"]` instead'
|
||||
)
|
||||
context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead')
|
||||
}
|
||||
|
||||
const freeformType = json["freeform"]?.["type"]
|
||||
if (freeformType) {
|
||||
if (Validators.availableTypes.indexOf(freeformType) < 0) {
|
||||
throw (
|
||||
"At " +
|
||||
context +
|
||||
".freeform.type is an unknown type: " +
|
||||
freeformType +
|
||||
"; try one of " +
|
||||
Validators.availableTypes.join(", ")
|
||||
)
|
||||
context
|
||||
.enters("freeform", "type")
|
||||
.err(
|
||||
"Unknown type: " +
|
||||
freeformType +
|
||||
"; try one of " +
|
||||
Validators.availableTypes.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -828,24 +698,21 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
this._doesImageExist = doesImageExist
|
||||
}
|
||||
|
||||
convert(
|
||||
json: LayerConfigJson,
|
||||
context: string
|
||||
): { result: LayerConfigJson; errors: string[]; warnings?: string[]; information?: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
const information = []
|
||||
context = "While validating a layer: " + context
|
||||
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
|
||||
context = context.inOperation(this.name)
|
||||
if (typeof json === "string") {
|
||||
errors.push(context + ": This layer hasn't been expanded: " + json)
|
||||
return {
|
||||
result: null,
|
||||
errors,
|
||||
}
|
||||
context.err("This layer hasn't been expanded: " + json)
|
||||
return null
|
||||
}
|
||||
|
||||
const layerConfig = new LayerConfig(json, "validation", true)
|
||||
for (const [attribute, code, isStrict] of layerConfig.calculatedTags ?? []) {
|
||||
let layerConfig: LayerConfig
|
||||
try {
|
||||
layerConfig = new LayerConfig(json, "validation", true)
|
||||
} catch (e) {
|
||||
context.err(e)
|
||||
return undefined
|
||||
}
|
||||
for (const [_, code, __] of layerConfig.calculatedTags ?? []) {
|
||||
try {
|
||||
new Function("feat", "return " + code + ";")
|
||||
} catch (e) {
|
||||
|
@ -855,9 +722,8 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
|
||||
if (json.source === "special") {
|
||||
if (!Constants.priviliged_layers.find((x) => x == json.id)) {
|
||||
errors.push(
|
||||
context +
|
||||
": layer " +
|
||||
context.err(
|
||||
"Layer " +
|
||||
json.id +
|
||||
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
|
||||
)
|
||||
|
@ -866,30 +732,27 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
|
||||
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
|
||||
if (json.title === undefined && json.source !== "special:library") {
|
||||
errors.push(
|
||||
context +
|
||||
": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
|
||||
context.err(
|
||||
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
|
||||
)
|
||||
}
|
||||
if (json.title === null) {
|
||||
information.push(
|
||||
context +
|
||||
": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
|
||||
context.info(
|
||||
"Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (json["builtin"] !== undefined) {
|
||||
errors.push(context + ": This layer hasn't been expanded: " + json)
|
||||
return {
|
||||
result: null,
|
||||
errors,
|
||||
}
|
||||
context.err("This layer hasn't been expanded: " + json)
|
||||
return null
|
||||
}
|
||||
|
||||
if (json.minzoom > Constants.minZoomLevelToAddNewPoint) {
|
||||
;(json.presets?.length > 0 ? errors : warnings).push(
|
||||
`At ${context}: minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates`
|
||||
const c = context.enter("minzoom")
|
||||
const w = json.presets?.length > 0 ? c.err : c.warn
|
||||
w(
|
||||
`Minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates`
|
||||
)
|
||||
}
|
||||
{
|
||||
|
@ -898,19 +761,17 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"])))
|
||||
)
|
||||
if (duplicates.length > 0) {
|
||||
console.log(json.tagRenderings)
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
": some tagrenderings have a duplicate id: " +
|
||||
duplicates.join(", ")
|
||||
)
|
||||
context
|
||||
.enter("tagRenderings")
|
||||
.err("Some tagrenderings have a duplicate id: " + duplicates.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
if (json.deletion !== undefined && json.deletion instanceof DeleteConfig) {
|
||||
if (json.deletion.softDeletionTags === undefined) {
|
||||
warnings.push("No soft-deletion tags in deletion block for layer " + json.id)
|
||||
context
|
||||
.enter("deletion")
|
||||
.warn("No soft-deletion tags in deletion block for layer " + json.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -919,7 +780,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
// Some checks for legacy elements
|
||||
|
||||
if (json["overpassTags"] !== undefined) {
|
||||
errors.push(
|
||||
context.err(
|
||||
"Layer " +
|
||||
json.id +
|
||||
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)'
|
||||
|
@ -938,18 +799,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
]
|
||||
for (const forbiddenKey of forbiddenTopLevel) {
|
||||
if (json[forbiddenKey] !== undefined)
|
||||
errors.push(
|
||||
context +
|
||||
": layer " +
|
||||
json.id +
|
||||
" still has a forbidden key " +
|
||||
forbiddenKey
|
||||
context.err(
|
||||
"Layer " + json.id + " still has a forbidden key " + forbiddenKey
|
||||
)
|
||||
}
|
||||
if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
|
||||
errors.push(
|
||||
context +
|
||||
": layer " +
|
||||
context.err(
|
||||
"Layer " +
|
||||
json.id +
|
||||
" contains an old 'hideUnderlayingFeaturesMinPercentage'"
|
||||
)
|
||||
|
@ -959,14 +815,14 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
json.isShown !== undefined &&
|
||||
(json.isShown["render"] !== undefined || json.isShown["mappings"] !== undefined)
|
||||
) {
|
||||
warnings.push(context + " has a tagRendering as `isShown`")
|
||||
context.warn("Has a tagRendering as `isShown`")
|
||||
}
|
||||
}
|
||||
if (this._isBuiltin) {
|
||||
// Check location of layer file
|
||||
const expected: string = `assets/layers/${json.id}/${json.id}.json`
|
||||
if (this._path != undefined && this._path.indexOf(expected) < 0) {
|
||||
errors.push(
|
||||
context.err(
|
||||
"Layer is in an incorrect place. The path is " +
|
||||
this._path +
|
||||
", but expected " +
|
||||
|
@ -984,11 +840,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
emptyIndexes.push(i)
|
||||
}
|
||||
}
|
||||
errors.push(
|
||||
`Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${context}.tagRenderings.[${emptyIndexes.join(
|
||||
","
|
||||
)}])`
|
||||
)
|
||||
context
|
||||
.enter(["tagRenderings", ...emptyIndexes])
|
||||
.err(
|
||||
`Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join(
|
||||
","
|
||||
)}])`
|
||||
)
|
||||
}
|
||||
|
||||
const duplicateIds = Utils.Duplicates(
|
||||
|
@ -997,29 +855,26 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
.filter((id) => id !== "questions")
|
||||
)
|
||||
if (duplicateIds.length > 0 && !Utils.runningFromConsole) {
|
||||
errors.push(
|
||||
`Some tagRenderings have a duplicate id: ${duplicateIds} (at ${context}.tagRenderings)`
|
||||
)
|
||||
context
|
||||
.enter("tagRenderings")
|
||||
.err(`Some tagRenderings have a duplicate id: ${duplicateIds}`)
|
||||
}
|
||||
|
||||
if (json.description === undefined) {
|
||||
if (typeof json.source === null) {
|
||||
errors.push(context + ": A priviliged layer must have a description")
|
||||
context.err("A priviliged layer must have a description")
|
||||
} else {
|
||||
warnings.push(context + ": A builtin layer should have a description")
|
||||
context.warn("A builtin layer should have a description")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json.filter) {
|
||||
const r = new On("filter", new Each(new ValidateFilter())).convert(json, context)
|
||||
warnings.push(...(r.warnings ?? []))
|
||||
errors.push(...(r.errors ?? []))
|
||||
information.push(...(r.information ?? []))
|
||||
new On("filter", new Each(new ValidateFilter())).convert(json, context)
|
||||
}
|
||||
|
||||
if (json.tagRenderings !== undefined) {
|
||||
const r = new On(
|
||||
new On(
|
||||
"tagRenderings",
|
||||
new Each(
|
||||
new ValidateTagRenderings(json, this._doesImageExist, {
|
||||
|
@ -1027,9 +882,6 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
})
|
||||
)
|
||||
).convert(json, context)
|
||||
warnings.push(...(r.warnings ?? []))
|
||||
errors.push(...(r.errors ?? []))
|
||||
information.push(...(r.information ?? []))
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1037,10 +889,8 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
(mr) => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined
|
||||
)
|
||||
if (hasCondition?.length > 0) {
|
||||
errors.push(
|
||||
"At " +
|
||||
context +
|
||||
":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" +
|
||||
context.err(
|
||||
"One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" +
|
||||
JSON.stringify(hasCondition, null, " ")
|
||||
)
|
||||
}
|
||||
|
@ -1048,7 +898,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
|
||||
if (json.presets !== undefined) {
|
||||
if (typeof json.source === "string") {
|
||||
throw "A special layer cannot have presets"
|
||||
context.err("A special layer cannot have presets")
|
||||
}
|
||||
// Check that a preset will be picked up by the layer itself
|
||||
const baseTags = TagUtils.Tag(json.source["osmTags"])
|
||||
|
@ -1063,28 +913,22 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
const doMatch = baseTags.matchesProperties(properties)
|
||||
if (!doMatch) {
|
||||
errors.push(
|
||||
context +
|
||||
".presets[" +
|
||||
i +
|
||||
"]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
|
||||
JSON.stringify(properties) +
|
||||
"\n The required tags are: " +
|
||||
baseTags.asHumanString(false, false, {})
|
||||
)
|
||||
context
|
||||
.enters("presets", i)
|
||||
.err(
|
||||
"This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
|
||||
JSON.stringify(properties) +
|
||||
"\n The required tags are: " +
|
||||
baseTags.asHumanString(false, false, {})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
errors.push(e)
|
||||
context.err(e)
|
||||
}
|
||||
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1093,33 +937,27 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
|
|||
super("Detect common errors in the filters", [], "ValidateFilter")
|
||||
}
|
||||
|
||||
convert(
|
||||
filter: FilterConfigJson,
|
||||
context: string
|
||||
): {
|
||||
result: FilterConfigJson
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
convert(filter: FilterConfigJson, context: ConversionContext): FilterConfigJson {
|
||||
if (typeof filter === "string") {
|
||||
// Calling another filter, we skip
|
||||
return { result: filter }
|
||||
return filter
|
||||
}
|
||||
const errors = []
|
||||
for (const option of filter.options) {
|
||||
for (let i = 0; i < option.fields?.length ?? 0; i++) {
|
||||
const field = option.fields[i]
|
||||
const type = field.type ?? "string"
|
||||
if (Validators.availableTypes.find((t) => t === type) === undefined) {
|
||||
const err = `Invalid filter: ${type} is not a valid textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from(
|
||||
Validators.availableTypes
|
||||
).join(",")}`
|
||||
errors.push(err)
|
||||
context
|
||||
.enters("fields", i)
|
||||
.err(
|
||||
`Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from(
|
||||
Validators.availableTypes
|
||||
).join(",")}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return { result: filter, errors }
|
||||
return filter
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1137,17 +975,8 @@ export class DetectDuplicateFilters extends DesugaringStep<{
|
|||
|
||||
convert(
|
||||
json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] },
|
||||
__: string
|
||||
): {
|
||||
result: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
const errors: string[] = []
|
||||
const warnings: string[] = []
|
||||
const information: string[] = []
|
||||
|
||||
context: ConversionContext
|
||||
): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } {
|
||||
const { layers, themes } = json
|
||||
const perOsmTag = new Map<
|
||||
string,
|
||||
|
@ -1191,15 +1020,10 @@ export class DetectDuplicateFilters extends DesugaringStep<{
|
|||
}
|
||||
msg += `\n - ${id}${layer.id}.${filter.id}`
|
||||
}
|
||||
warnings.push(msg)
|
||||
context.warn(msg)
|
||||
})
|
||||
|
||||
return {
|
||||
result: json,
|
||||
errors,
|
||||
warnings,
|
||||
information,
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1258,18 +1082,10 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
"DetectDuplicatePresets"
|
||||
)
|
||||
}
|
||||
convert(
|
||||
json: LayoutConfig,
|
||||
context: string
|
||||
): {
|
||||
result: LayoutConfig
|
||||
errors?: string[]
|
||||
warnings?: string[]
|
||||
information?: string[]
|
||||
} {
|
||||
|
||||
convert(json: LayoutConfig, context: ConversionContext): LayoutConfig {
|
||||
const presets: PresetConfig[] = [].concat(...json.layers.map((l) => l.presets))
|
||||
|
||||
const errors = []
|
||||
const enNames = presets.map((p) => p.title.textFor("en"))
|
||||
if (new Set(enNames).size != enNames.length) {
|
||||
const dups = Utils.Duplicates(enNames)
|
||||
|
@ -1277,8 +1093,8 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0)
|
||||
)
|
||||
const layerIds = layersWithDup.map((l) => l.id)
|
||||
errors.push(
|
||||
`At ${context}: this themes has multiple presets which are named:${dups}, namely layers ${layerIds.join(
|
||||
context.err(
|
||||
`This themes has multiple presets which are named:${dups}, namely layers ${layerIds.join(
|
||||
", "
|
||||
)} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`
|
||||
)
|
||||
|
@ -1298,8 +1114,8 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
presetB.preciseInput.snapToLayers
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
`At ${context}: this themes has multiple presets with the same tags: ${presetATags.asHumanString(
|
||||
context.err(
|
||||
`This themes has multiple presets with the same tags: ${presetATags.asHumanString(
|
||||
false,
|
||||
false,
|
||||
{}
|
||||
|
@ -1311,6 +1127,6 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
return { errors, result: json }
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
|||
*/
|
||||
freeform?: {
|
||||
/**
|
||||
* question What is the name of the attribute that should be written to?
|
||||
* question: What is the name of the attribute that should be written to?
|
||||
* ifunset: do not offer a freeform textfield as answer option
|
||||
*/
|
||||
key: string
|
||||
|
@ -206,11 +206,14 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
|||
* question: What is the input type?
|
||||
* The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...
|
||||
* See Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values
|
||||
* ifunset: use an unconstrained <b>string</b> as input (default)
|
||||
* suggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: "value="+type.name, then: "<b>"+type.name+"</b> "+type.explanation.split("\n")[0]}))
|
||||
*/
|
||||
type?: string
|
||||
/**
|
||||
* question: What placeholder text should be shown in the input-element if there is no input?
|
||||
* A (translated) text that is shown (as gray text) within the textfield
|
||||
* type: translation
|
||||
*/
|
||||
placeholder?: string | any
|
||||
|
||||
|
@ -236,8 +239,9 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
|||
inline?: boolean
|
||||
|
||||
/**
|
||||
* default value to enter if no previous tagging is present.
|
||||
* Normally undefined (aka do not enter anything)
|
||||
* question: What value should be entered in the text field if no value is set?
|
||||
* This can help people to quickly enter the most common option
|
||||
* ifunset: do not prefill the textfield
|
||||
*/
|
||||
default?: string
|
||||
}
|
||||
|
|
|
@ -239,7 +239,9 @@ export default class LayerConfig extends WithContextLoader {
|
|||
throw (
|
||||
"Layer " +
|
||||
this.id +
|
||||
" defines a maxSnapDistance, but does not include a `snapToLayer`"
|
||||
" defines a maxSnapDistance, but does not include a `snapToLayer` (at " +
|
||||
context +
|
||||
")"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Utils } from "../../Utils"
|
|||
import LanguageUtils from "../../Utils/LanguageUtils"
|
||||
|
||||
import { RasterLayerProperties } from "../RasterLayerProperties"
|
||||
import { ConversionContext } from "./Conversion/Conversion"
|
||||
|
||||
/**
|
||||
* Minimal information about a theme
|
||||
|
@ -97,10 +98,7 @@ export default class LayoutConfig implements LayoutInformation {
|
|||
this.language = json.mustHaveLanguage ?? Object.keys(json.title)
|
||||
this.usedImages = Array.from(
|
||||
new ExtractImages(official, undefined)
|
||||
.convertStrict(
|
||||
json,
|
||||
"while extracting the images of " + json.id + " " + context ?? ""
|
||||
)
|
||||
.convertStrict(json, ConversionContext.construct([json.id], ["ExtractImages"]))
|
||||
.map((i) => i.path)
|
||||
).sort()
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ import { VariableUiElement } from "../../UI/Base/VariableUIElement"
|
|||
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
|
||||
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
|
||||
import Marker from "../../UI/Map/Marker.svelte"
|
||||
import DynamicMarker from "../../UI/Map/DynamicMarker.svelte"
|
||||
|
||||
export class IconConfig extends WithContextLoader {
|
||||
public readonly icon: TagRenderingConfig
|
||||
|
@ -45,8 +46,7 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
"point" | "centroid" | "start" | "end" | "projected_centerpoint" | string
|
||||
>
|
||||
|
||||
// public readonly icon?: TagRenderingConfig
|
||||
private readonly marker: IconConfig[]
|
||||
public readonly marker: IconConfig[]
|
||||
public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[]
|
||||
public readonly iconSize: TagRenderingConfig
|
||||
public readonly anchor: TagRenderingConfig
|
||||
|
@ -192,7 +192,7 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
}
|
||||
|
||||
public GetBaseIcon(tags?: Record<string, string>): BaseUIElement {
|
||||
return new SvelteUIElement(Marker, { config: this, tags: new ImmutableStore(tags) })
|
||||
return new SvelteUIElement(DynamicMarker, { config: this, tags: new ImmutableStore(tags) })
|
||||
}
|
||||
public RenderIcon(
|
||||
tags: Store<Record<string, string>>,
|
||||
|
@ -244,7 +244,9 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
anchorH = -iconH / 2
|
||||
}
|
||||
|
||||
const icon = new SvelteUIElement(Marker, { config: this, tags }).SetClass("w-full h-full")
|
||||
const icon = new SvelteUIElement(DynamicMarker, { config: this, tags }).SetClass(
|
||||
"w-full h-full"
|
||||
)
|
||||
let badges = undefined
|
||||
if (options?.includeBadges ?? true) {
|
||||
badges = this.GetBadges(tags)
|
||||
|
|
41
src/UI/Map/DynamicIcon.svelte
Normal file
41
src/UI/Map/DynamicIcon.svelte
Normal file
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import Pin from "../../assets/svg/Pin.svelte";
|
||||
import Square from "../../assets/svg/Square.svelte";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
import Checkmark from "../../assets/svg/Checkmark.svelte";
|
||||
import Clock from "../../assets/svg/Clock.svelte";
|
||||
import Close from "../../assets/svg/Close.svelte";
|
||||
import Crosshair from "../../assets/svg/Crosshair.svelte";
|
||||
import Help from "../../assets/svg/Help.svelte";
|
||||
import Home from "../../assets/svg/Home.svelte";
|
||||
import Invalid from "../../assets/svg/Invalid.svelte";
|
||||
import Location from "../../assets/svg/Location.svelte";
|
||||
import Location_empty from "../../assets/svg/Location_empty.svelte";
|
||||
import Location_locked from "../../assets/svg/Location_locked.svelte";
|
||||
import Note from "../../assets/svg/Note.svelte";
|
||||
import Resolved from "../../assets/svg/Resolved.svelte";
|
||||
import Ring from "../../assets/svg/Ring.svelte";
|
||||
import Scissors from "../../assets/svg/Scissors.svelte";
|
||||
import Teardrop from "../../assets/svg/Teardrop.svelte";
|
||||
import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte";
|
||||
import Triangle from "../../assets/svg/Triangle.svelte";
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
/**
|
||||
* Renders a single icon.
|
||||
*
|
||||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
export let icon: IconConfig;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
let iconItem = icon.icon?.GetRenderValue(tags)?.txt;
|
||||
$: iconItem = icon.icon?.GetRenderValue($tags)?.txt;
|
||||
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000";
|
||||
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000";
|
||||
|
||||
</script>
|
||||
|
||||
<Icon icon={iconItem} {color}/>
|
21
src/UI/Map/DynamicMarker.svelte
Normal file
21
src/UI/Map/DynamicMarker.svelte
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
|
||||
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import DynamicIcon from "./DynamicIcon.svelte";
|
||||
|
||||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let config: PointRenderingConfig;
|
||||
let icons: IconConfig[] = config.marker;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
</script>
|
||||
{#if config !== undefined}
|
||||
<div class="relative w-full h-full">
|
||||
{#each icons as icon}
|
||||
<DynamicIcon {icon} {tags} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
|
@ -1,6 +1,4 @@
|
|||
<script lang="ts">
|
||||
import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
import Pin from "../../assets/svg/Pin.svelte";
|
||||
import Square from "../../assets/svg/Square.svelte";
|
||||
import Circle from "../../assets/svg/Circle.svelte";
|
||||
|
@ -27,60 +25,56 @@
|
|||
*
|
||||
* Icons -placed on top of each other- form a 'Marker' together
|
||||
*/
|
||||
export let icon: IconConfig;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
|
||||
let iconItem = icon.icon?.GetRenderValue(tags)?.txt;
|
||||
$: iconItem = icon.icon?.GetRenderValue($tags)?.txt;
|
||||
let color = icon.color?.GetRenderValue(tags)?.txt ?? "#000000";
|
||||
$: color = icon.color?.GetRenderValue($tags)?.txt ?? "#000000";
|
||||
export let icon: string | undefined;
|
||||
export let color: string | undefined;
|
||||
|
||||
</script>
|
||||
|
||||
{#if iconItem}
|
||||
{#if icon}
|
||||
<div class="absolute top-0 left-0 w-full h-full">
|
||||
{#if iconItem === "pin"}
|
||||
{#if icon === "pin"}
|
||||
<Pin {color} />
|
||||
{:else if iconItem === "square"}
|
||||
{:else if icon === "square"}
|
||||
<Square {color} />
|
||||
{:else if iconItem === "circle"}
|
||||
{:else if icon === "circle"}
|
||||
<Circle {color} />
|
||||
{:else if iconItem === "checkmark"}
|
||||
{:else if icon === "checkmark"}
|
||||
<Checkmark {color} />
|
||||
{:else if iconItem === "clock"}
|
||||
{:else if icon === "clock"}
|
||||
<Clock {color} />
|
||||
{:else if iconItem === "close"}
|
||||
{:else if icon === "close"}
|
||||
<Close {color} />
|
||||
{:else if iconItem === "crosshair"}
|
||||
{:else if icon === "crosshair"}
|
||||
<Crosshair {color} />
|
||||
{:else if iconItem === "help"}
|
||||
{:else if icon === "help"}
|
||||
<Help {color} />
|
||||
{:else if iconItem === "home"}
|
||||
{:else if icon === "home"}
|
||||
<Home {color} />
|
||||
{:else if iconItem === "invalid"}
|
||||
{:else if icon === "invalid"}
|
||||
<Invalid {color} />
|
||||
{:else if iconItem === "location"}
|
||||
{:else if icon === "location"}
|
||||
<Location {color} />
|
||||
{:else if iconItem === "location_empty"}
|
||||
{:else if icon === "location_empty"}
|
||||
<Location_empty {color} />
|
||||
{:else if iconItem === "location_locked"}
|
||||
{:else if icon === "location_locked"}
|
||||
<Location_locked {color} />
|
||||
{:else if iconItem === "note"}
|
||||
{:else if icon === "note"}
|
||||
<Note {color} />
|
||||
{:else if iconItem === "resolved"}
|
||||
{:else if icon === "resolved"}
|
||||
<Resolved {color} />
|
||||
{:else if iconItem === "ring"}
|
||||
{:else if icon === "ring"}
|
||||
<Ring {color} />
|
||||
{:else if iconItem === "scissors"}
|
||||
{:else if icon === "scissors"}
|
||||
<Scissors {color} />
|
||||
{:else if iconItem === "teardrop"}
|
||||
{:else if icon === "teardrop"}
|
||||
<Teardrop {color} />
|
||||
{:else if iconItem === "teardrop_with_hole_green"}
|
||||
{:else if icon === "teardrop_with_hole_green"}
|
||||
<Teardrop_with_hole_green {color} />
|
||||
{:else if iconItem === "triangle"}
|
||||
{:else if icon === "triangle"}
|
||||
<Triangle {color} />
|
||||
{:else}
|
||||
<img class="w-full h-full" src={iconItem} />
|
||||
<img class="w-full h-full" src={icon} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
<script lang="ts">
|
||||
|
||||
import PointRenderingConfig, { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig";
|
||||
import Icon from "./Icon.svelte";
|
||||
import { Store } from "../../Logic/UIEventSource";
|
||||
|
||||
/**
|
||||
* Renders a 'marker', which consists of multiple 'icons'
|
||||
*/
|
||||
export let config: PointRenderingConfig;
|
||||
let icons: IconConfig[] = config.marker;
|
||||
export let tags: Store<Record<string, string>>;
|
||||
export let icons: { icon: string, color: string }[]
|
||||
|
||||
</script>
|
||||
{#if config !== undefined}
|
||||
{#if icons !== undefined && icons.length > 0}
|
||||
<div class="relative w-full h-full">
|
||||
{#each icons as icon}
|
||||
<Icon {icon} {tags} />
|
||||
<Icon icon={icon.icon} color={icon.color} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -235,6 +235,7 @@
|
|||
bind:group={selectedMapping}
|
||||
name={"mappings-radio-" + config.id}
|
||||
value={i}
|
||||
on:keypress={e => {console.log(e) ; if(e.key === "Enter") onSave()}}
|
||||
/>
|
||||
</TagRenderingMappingInput>
|
||||
{/each}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* Blacklist of regions for the general area tab
|
||||
* These are regions which are handled by a different tab
|
||||
*/
|
||||
const regionBlacklist = ["hidden", undefined, "infobox", "tagrenderings", "maprendering", "editing", "title"];
|
||||
const regionBlacklist = ["hidden", undefined, "infobox", "tagrenderings", "maprendering", "editing", "title","linerendering","pointrendering"];
|
||||
const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group));
|
||||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {};
|
||||
|
@ -27,7 +27,7 @@
|
|||
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region);
|
||||
}
|
||||
|
||||
const baselayerRegions: string[] = ["Basic", "presets", "filters", "advanced", "expert"];
|
||||
const baselayerRegions: string[] = ["Basic", "presets", "filters"];
|
||||
for (const baselayerRegion of baselayerRegions) {
|
||||
if (perRegion[baselayerRegion] === undefined) {
|
||||
console.error("BaseLayerRegions in editLayer: no items have group '" + baselayerRegion + "\"");
|
||||
|
@ -38,8 +38,6 @@
|
|||
</script>
|
||||
|
||||
<h3>Editing layer {$title}</h3>
|
||||
<h4>Leftover regions</h4>
|
||||
{leftoverRegions.join("; ")}
|
||||
<div class="m4">
|
||||
<TabbedGroup tab={new UIEventSource(2)}>
|
||||
<div slot="title0">General properties</div>
|
||||
|
@ -47,9 +45,6 @@
|
|||
{#each baselayerRegions as region}
|
||||
<Region {state} configs={perRegion[region]} title={region} />
|
||||
{/each}
|
||||
{#each leftoverRegions as region}
|
||||
<Region {state} configs={perRegion[region]} title={region} />
|
||||
{/each}
|
||||
</div>
|
||||
<div slot="title1">Information panel (questions and answers)</div>
|
||||
<div slot="content1">
|
||||
|
@ -63,8 +58,14 @@
|
|||
<Region configs={perRegion["linerendering"]} {state} />
|
||||
<Region configs={perRegion["pointrendering"]} {state} />
|
||||
</div>
|
||||
<div slot="title3">Configuration file</div>
|
||||
|
||||
<div slot="title3">Advanced functionality</div>
|
||||
<div slot="content3">
|
||||
<Region configs={perRegion["advanced"]} {state} />
|
||||
<Region configs={perRegion["expert"]} {state} />
|
||||
</div>
|
||||
<div slot="title4">Configuration file</div>
|
||||
<div slot="content4">
|
||||
<div>
|
||||
Below, you'll find the raw configuration file in `.json`-format.
|
||||
This is mostly for debugging purposes
|
||||
|
|
|
@ -65,8 +65,13 @@ console.log("For ", schema.path, "got subparts", subparts)
|
|||
}
|
||||
|
||||
function del(value) {
|
||||
values.data.splice(values.data.indexOf(value));
|
||||
const index = values.data.indexOf(value)
|
||||
console.log("Deleting",value, index)
|
||||
values.data.splice(index, 1);
|
||||
const store = <UIEventSource<[]>>state.getStoreFor(path);
|
||||
store.data.splice(index, 1)
|
||||
values.ping();
|
||||
store.ping()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
@ -96,13 +96,13 @@
|
|||
err = path.join(".") + " " + e
|
||||
}
|
||||
let startValue = state.getCurrentValueFor(path)
|
||||
if (typeof startValue !== "string") {
|
||||
startValue = JSON.stringify(startValue)
|
||||
}
|
||||
const tags = new UIEventSource<Record<string, string>>({value: startValue ?? ""})
|
||||
try {
|
||||
onDestroy(state.register(path, tags.map(tgs => {
|
||||
const v = tgs["value"];
|
||||
if(typeof v !== "string"){
|
||||
return v
|
||||
}
|
||||
if (schema.type === "boolan") {
|
||||
return v === "true" || v === "yes" || v === "1"
|
||||
}
|
||||
|
@ -135,7 +135,6 @@
|
|||
<span class="alert">{err}</span>
|
||||
{:else}
|
||||
<div class="w-full flex flex-col">
|
||||
<span class="subtle">{path.join(".")}</span>
|
||||
<TagRenderingEditable {config} selectedElement={undefined} showQuestionIfUnknown={true} {state} {tags}/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -214,5 +214,4 @@
|
|||
path={[...subpath, (subschema?.path?.at(-1) ?? "???")]}></SchemaBasedInput>
|
||||
{/each}
|
||||
{/if}
|
||||
{chosenOption}
|
||||
</div>
|
||||
|
|
38
src/UI/Studio/StudioServer.ts
Normal file
38
src/UI/Studio/StudioServer.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Utils } from "../../Utils"
|
||||
import Constants from "../../Models/Constants"
|
||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
|
||||
export default class StudioServer {
|
||||
private _url: string
|
||||
|
||||
constructor(url: string) {
|
||||
this._url = url
|
||||
}
|
||||
|
||||
public async fetchLayerOverview(): Promise<Set<string>> {
|
||||
const { allFiles } = <{ allFiles: string[] }>(
|
||||
await Utils.downloadJson(this._url + "/overview")
|
||||
)
|
||||
const layers = allFiles
|
||||
.filter((f) => f.startsWith("layers/"))
|
||||
.map((l) => l.substring(l.lastIndexOf("/") + 1, l.length - ".json".length))
|
||||
.filter((layerId) => Constants.priviliged_layers.indexOf(<any>layerId) < 0)
|
||||
return new Set<string>(layers)
|
||||
}
|
||||
|
||||
async fetchLayer(layerId: string, checkNew: boolean = false): Promise<LayerConfigJson> {
|
||||
try {
|
||||
return await Utils.downloadJson(
|
||||
this._url +
|
||||
"/layers/" +
|
||||
layerId +
|
||||
"/" +
|
||||
layerId +
|
||||
".json" +
|
||||
(checkNew ? ".new.json" : "")
|
||||
)
|
||||
} catch (e) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,35 +2,32 @@
|
|||
|
||||
|
||||
import NextButton from "./Base/NextButton.svelte";
|
||||
import { Utils } from "../Utils";
|
||||
import { UIEventSource } from "../Logic/UIEventSource";
|
||||
import Constants from "../Models/Constants";
|
||||
import ValidatedInput from "./InputElement/ValidatedInput.svelte";
|
||||
import EditLayerState from "./Studio/EditLayerState";
|
||||
import EditLayer from "./Studio/EditLayer.svelte";
|
||||
import Loading from "../assets/svg/Loading.svelte";
|
||||
import Marker from "./Map/Marker.svelte";
|
||||
import { AllSharedLayers } from "../Customizations/AllSharedLayers";
|
||||
import StudioServer from "./Studio/StudioServer";
|
||||
import LoginToggle from "./Base/LoginToggle.svelte";
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection";
|
||||
import { QueryParameters } from "../Logic/Web/QueryParameters";
|
||||
|
||||
|
||||
export let studioUrl = "http://127.0.0.1:1235";
|
||||
let overview = UIEventSource.FromPromise<{ allFiles: string[] }>(Utils.downloadJson(studioUrl + "/overview"));
|
||||
let layers = overview.map(overview => {
|
||||
if (!overview) {
|
||||
return [];
|
||||
}
|
||||
return overview.allFiles.filter(f => f.startsWith("layers/")
|
||||
).map(l => l.substring(l.lastIndexOf("/") + 1, l.length - ".json".length))
|
||||
.filter(layerId => Constants.priviliged_layers.indexOf(layerId) < 0);
|
||||
});
|
||||
const studio = new StudioServer(studioUrl);
|
||||
let layers = UIEventSource.FromPromise(studio.fetchLayerOverview());
|
||||
let state: undefined | "edit_layer" | "new_layer" | "edit_theme" | "new_theme" | "editing_layer" | "loading" = undefined;
|
||||
|
||||
let initialLayerConfig: undefined;
|
||||
let initialLayerConfig: { id: string };
|
||||
let newLayerId = new UIEventSource<string>("");
|
||||
let layerIdFeedback = new UIEventSource<string>(undefined);
|
||||
newLayerId.addCallbackD(layerId => {
|
||||
if (layerId === "") {
|
||||
return;
|
||||
}
|
||||
if (layers.data.indexOf(layerId) >= 0) {
|
||||
if (layers.data.has(layerId)) {
|
||||
layerIdFeedback.setData("This id is already used");
|
||||
}
|
||||
}, [layers]);
|
||||
|
@ -38,7 +35,28 @@
|
|||
|
||||
let editLayerState = new EditLayerState();
|
||||
|
||||
function fetchIconDescription(layerId): any {
|
||||
const icon = AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
|
||||
console.log(icon);
|
||||
return icon;
|
||||
}
|
||||
|
||||
let osmConnection = new OsmConnection( new OsmConnection({
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
),
|
||||
}))
|
||||
|
||||
</script>
|
||||
|
||||
<LoginToggle state={{osmConnection}}>
|
||||
<div slot="not-logged-in" >
|
||||
<NextButton clss="primary">
|
||||
Please log in to use MapComplete Studio
|
||||
</NextButton>
|
||||
</div>
|
||||
{#if state === undefined}
|
||||
<h1>MapComplete Studio</h1>
|
||||
<div class="w-full flex flex-col">
|
||||
|
@ -58,13 +76,16 @@
|
|||
</div>
|
||||
{:else if state === "edit_layer"}
|
||||
<div class="flex flex-wrap">
|
||||
{#each $layers as layerId}
|
||||
{#each Array.from($layers) as layerId}
|
||||
<NextButton clss="small" on:click={async () => {
|
||||
console.log("Editing layer",layerId)
|
||||
state = "loading"
|
||||
initialLayerConfig = await Utils.downloadJson(studioUrl+"/layers/"+layerId+"/"+layerId+".json")
|
||||
initialLayerConfig = await studio.fetchLayer(layerId)
|
||||
state = "editing_layer"
|
||||
}}>
|
||||
<div class="w-4 h-4 mr-1">
|
||||
<Marker icons={fetchIconDescription(layerId)} />
|
||||
</div>
|
||||
{layerId}
|
||||
</NextButton>
|
||||
{/each}
|
||||
|
@ -76,12 +97,22 @@
|
|||
{$layerIdFeedback}
|
||||
</div>
|
||||
{:else }
|
||||
<NextButton on:click={() => {initialLayerConfig = ({id: newLayerId.data}); state = "editing_layer"}}>
|
||||
<NextButton on:click={async () => {
|
||||
state = "loading"
|
||||
const id = newLayerId.data
|
||||
const createdBy = osmConnection.userDetails.data.name
|
||||
|
||||
const loaded = await studio.fetchLayer(id, true)
|
||||
initialLayerConfig = loaded ?? {id, credits: createdBy};
|
||||
state = "editing_layer"}}>
|
||||
Create this layer
|
||||
</NextButton>
|
||||
{/if}
|
||||
{:else if state === "loading"}
|
||||
<Loading />
|
||||
<div class="w-8 h-8">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else if state === "editing_layer"}
|
||||
<EditLayer {initialLayerConfig} />
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
|
|
@ -11745,6 +11745,10 @@
|
|||
"if": "value=gps_track",
|
||||
"then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track."
|
||||
},
|
||||
{
|
||||
"if": "value=guidepost",
|
||||
"then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
|
||||
},
|
||||
{
|
||||
"if": "value=hackerspace",
|
||||
"then": "hackerspace - Hackerspace"
|
||||
|
@ -12102,15 +12106,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -12129,7 +12133,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -12816,10 +12820,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -12830,6 +12835,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -12902,6 +12908,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -12915,7 +12925,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -12962,9 +12975,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -13837,10 +13853,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -13852,6 +13869,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -13924,6 +13942,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -13938,7 +13960,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -13989,9 +14014,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -14888,10 +14916,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -14903,6 +14932,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -14975,6 +15005,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -14989,7 +15023,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -15040,9 +15077,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -15951,10 +15991,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -15967,6 +16008,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -16039,6 +16081,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -16054,7 +16100,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -16109,9 +16158,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
|
|
@ -663,15 +663,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -690,7 +690,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -13206,6 +13206,10 @@
|
|||
"if": "value=gps_track",
|
||||
"then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track."
|
||||
},
|
||||
{
|
||||
"if": "value=guidepost",
|
||||
"then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
|
||||
},
|
||||
{
|
||||
"if": "value=hackerspace",
|
||||
"then": "hackerspace - Hackerspace"
|
||||
|
@ -13565,15 +13569,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -13592,7 +13596,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -14300,10 +14304,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -14315,6 +14320,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -14387,6 +14393,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -14401,7 +14411,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -14452,9 +14465,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -15363,10 +15379,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -15379,6 +15396,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -15451,6 +15469,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -15466,7 +15488,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -15521,9 +15546,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -16457,10 +16485,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -16473,6 +16502,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -16545,6 +16575,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -16560,7 +16594,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -16615,9 +16652,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -17562,10 +17602,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -17579,6 +17620,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -17651,6 +17693,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -17667,7 +17713,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -17726,9 +17775,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -31420,6 +31472,10 @@
|
|||
"if": "value=gps_track",
|
||||
"then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track."
|
||||
},
|
||||
{
|
||||
"if": "value=guidepost",
|
||||
"then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
|
||||
},
|
||||
{
|
||||
"if": "value=hackerspace",
|
||||
"then": "hackerspace - Hackerspace"
|
||||
|
@ -31781,15 +31837,15 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained <b>string</b> as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"<b>\"+type.name+\"</b> \"+type.explanation.split(\"\\n\")[0]}))",
|
||||
"type": "string"
|
||||
},
|
||||
"placeholder": {
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
"description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
|
||||
},
|
||||
"helperArgs": {
|
||||
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
|
||||
|
@ -31808,7 +31864,7 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"default": {
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
|
||||
"description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -32537,10 +32593,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -32553,6 +32610,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -32625,6 +32683,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -32640,7 +32702,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -32695,9 +32760,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -33642,10 +33710,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -33659,6 +33728,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -33731,6 +33801,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -33747,7 +33821,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -33806,9 +33883,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -34779,10 +34859,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -34796,6 +34877,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -34868,6 +34950,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -34884,7 +34970,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -34943,9 +35032,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -35926,10 +36018,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -35944,6 +36037,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -36016,6 +36110,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -36033,7 +36131,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -36096,9 +36197,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
|
|
@ -473,10 +473,11 @@
|
|||
],
|
||||
"required": true,
|
||||
"hints": {
|
||||
"question": "What is the name of the attribute that should be written to?",
|
||||
"ifunset": "do not offer a freeform textfield as answer option"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "question What is the name of the attribute that should be written to?"
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -486,6 +487,7 @@
|
|||
"required": false,
|
||||
"hints": {
|
||||
"question": "What is the input type?",
|
||||
"ifunset": "use an unconstrained <b>string</b> as input (default)",
|
||||
"suggestions": [
|
||||
{
|
||||
"if": "value=string",
|
||||
|
@ -558,6 +560,10 @@
|
|||
{
|
||||
"if": "value=fediverse",
|
||||
"then": "<b>fediverse</b> Validates fediverse addresses and normalizes them into `@username@server`-format"
|
||||
},
|
||||
{
|
||||
"if": "value=id",
|
||||
"then": "<b>id</b> Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -570,7 +576,10 @@
|
|||
"placeholder"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"typehint": "translation",
|
||||
"question": "What placeholder text should be shown in the input-element if there is no input?"
|
||||
},
|
||||
"description": "A (translated) text that is shown (as gray text) within the textfield"
|
||||
},
|
||||
{
|
||||
|
@ -613,9 +622,12 @@
|
|||
"default"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"hints": {
|
||||
"question": "What value should be entered in the text field if no value is set?",
|
||||
"ifunset": "do not prefill the textfield"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
|
||||
"description": "This can help people to quickly enter the most common option"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
|
Loading…
Reference in a new issue