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