diff --git a/Docs/Schemas/LayerConfigJson.schema.json b/Docs/Schemas/LayerConfigJson.schema.json index a3b9cb452..62a369946 100644 --- a/Docs/Schemas/LayerConfigJson.schema.json +++ b/Docs/Schemas/LayerConfigJson.schema.json @@ -1169,7 +1169,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1177,7 +1177,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -1379,7 +1387,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1387,7 +1395,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", diff --git a/Docs/Schemas/LayerConfigJsonJSC.ts b/Docs/Schemas/LayerConfigJsonJSC.ts index 86a32ab8c..ea09cdf9c 100644 --- a/Docs/Schemas/LayerConfigJsonJSC.ts +++ b/Docs/Schemas/LayerConfigJsonJSC.ts @@ -1156,7 +1156,7 @@ export default { "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1164,7 +1164,15 @@ export default { "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -1365,7 +1373,7 @@ export default { "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1373,7 +1381,15 @@ export default { "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", diff --git a/Docs/Schemas/LayoutConfigJson.schema.json b/Docs/Schemas/LayoutConfigJson.schema.json index 4078fbd26..1716b6715 100644 --- a/Docs/Schemas/LayoutConfigJson.schema.json +++ b/Docs/Schemas/LayoutConfigJson.schema.json @@ -1081,7 +1081,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1089,7 +1089,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -1291,7 +1299,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1299,7 +1307,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", diff --git a/Docs/Schemas/LayoutConfigJsonJSC.ts b/Docs/Schemas/LayoutConfigJsonJSC.ts index 052ddf813..018b0cb22 100644 --- a/Docs/Schemas/LayoutConfigJsonJSC.ts +++ b/Docs/Schemas/LayoutConfigJsonJSC.ts @@ -1068,7 +1068,7 @@ export default { "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1076,7 +1076,15 @@ export default { "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -1277,7 +1285,7 @@ export default { "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1285,7 +1293,15 @@ export default { "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", diff --git a/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json b/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json index 13ef8e5a1..c4bb52262 100644 --- a/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json +++ b/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json @@ -21,7 +21,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -29,7 +29,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", diff --git a/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts b/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts index 42d79644c..7c064499e 100644 --- a/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts @@ -21,7 +21,7 @@ export default { "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -29,7 +29,15 @@ export default { "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", diff --git a/Docs/UserTests/2023-10-31 User Test Studio Shareish.md b/Docs/UserTests/2023-10-31 User Test Studio Shareish.md new file mode 100644 index 000000000..28ef6b838 --- /dev/null +++ b/Docs/UserTests/2023-10-31 User Test Studio Shareish.md @@ -0,0 +1,19 @@ +# User Test of the Studio + +## Task + +Create a simple layer specification using MapComplete studio with 'images', a few questions and an icon. The actual _topic_ of the layer can be chosen by the participant + +This participant wanted to create a layer about food_sharing and give_boxes. + +## Background info + +User has used mapcomplete a few times before but has very little OSM-knowledge. + +## Surfaced issues + +- [ ] dev.mapcomplete.org crashes +- [x] Switching tagRenderings or creating them sometimes creates a 'null' value which crashes downstream: should be filtered out +- [x] Switching between editing layers does not update the title +- [x] The warning messages don't update when editing +- [x] A questionHint without a question should give an error diff --git a/assets/layers/bicycle_library/bicycle_library.json b/assets/layers/bicycle_library/bicycle_library.json index 014e037e7..4200be8ea 100644 --- a/assets/layers/bicycle_library/bicycle_library.json +++ b/assets/layers/bicycle_library/bicycle_library.json @@ -308,7 +308,6 @@ "nl": "Aanbod voor kinderen", "en": "Bikes for children available", "fr": "Vélos pour enfants disponibles", - "hu": "", "it": "Sono disponibili biciclette per bambini", "de": "Fahrräder für Kinder verfügbar", "ru": "Доступны детские велосипеды", diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 1f77c30fc..1878f1ffe 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -10,17 +10,7 @@ "es": "Estaciones de carga", "pl": "Stacje ładowania" }, - "description": { - "en": "A charging station", - "nl": "Oplaadpunten", - "ca": "Una estació de càrrega", - "cs": "Nabíjecí stanice", - "da": "En ladestation", - "de": "Eine Ladestation", - "es": "Una estación de carga", - "fr": "Une station de recharge", - "pl": "Stacja ładowania" - }, + "minzoom": 10, "source": { "osmTags": { "and": [ @@ -35,7 +25,6 @@ ] } }, - "minzoom": 10, "title": { "render": { "en": "Charging station", @@ -91,113 +80,18 @@ } ] }, - "pointRendering": [ - { - "location": [ - "point", - "centroid" - ], - "marker": [ - { - "icon": "pin", - "color": "#fff" - }, - { - "icon": { - "render": "./assets/themes/charging_stations/plug.svg", - "mappings": [ - { - "if": "bicycle=yes", - "then": "./assets/themes/charging_stations/bicycle.svg" - }, - { - "if": { - "or": [ - "car=yes", - "motorcar=yes" - ] - }, - "then": "./assets/themes/charging_stations/car.svg" - } - ] - } - } - ], - "iconBadges": [ - { - "if": { - "or": [ - "disused:amenity=charging_station", - "operational_status=broken" - ] - }, - "then": "close:#c22;" - }, - { - "if": { - "or": [ - "proposed:amenity=charging_station", - "planned:amenity=charging_station" - ] - }, - "then": "./assets/layers/charging_station/under_construction.svg" - }, - { - "if": { - "and": [ - "bicycle=yes", - { - "or": [ - "motorcar=yes", - "car=yes" - ] - } - ] - }, - "then": "circle:#fff;./assets/themes/charging_stations/car.svg" - } - ], - "anchor": "bottom", - "iconSize": "50,50" - } - ], - "lineRendering": [], - "presets": [ - { - "tags": [ - "amenity=charging_station", - "motorcar=no", - "bicycle=yes", - "socket:typee=1" - ], - "title": { - "en": "a charging station for electrical bikes with a normal european wall plug (meant to charge electrical bikes)", - "nl": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact (speciaal bedoeld voor fietsen)", - "ca": "una estació de càrrega per a bicicletes elèctriques amb un endoll de paret europeu normal (destinat a carregar bicicletes elèctriques)", - "cs": "nabíjecí stanice pro elektrokola s běžnou evropskou zástrčkou (určeno k nabíjení elektrických kol)", - "da": "en ladestation til elektriske cykler med et normalt europæisk vægstik (beregnet til opladning af elektriske cykler)", - "de": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose (zum Laden von Elektrofahrrädern)", - "es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal (pensado para cargar bicicletas eléctricas)" - } - }, - { - "tags": [ - "amenity=charging_station", - "motorcar=yes", - "bicycle=no" - ], - "title": { - "en": "a charging station for cars", - "nl": "een oplaadstation voor elektrische auto's", - "ca": "una estació de càrrega per a cotxes", - "cs": "nabíjecí stanice pro automobily", - "da": "en ladestation til biler", - "de": "Eine Ladestation für Elektrofahrzeuge", - "es": "una estación de carga para coches", - "pl": "stacja ładowania dla samochodów" - } - } - ], + "description": { + "en": "A charging station", + "nl": "Oplaadpunten", + "ca": "Una estació de càrrega", + "cs": "Nabíjecí stanice", + "da": "En ladestation", + "de": "Eine Ladestation", + "es": "Una estación de carga", + "fr": "Une station de recharge", + "pl": "Stacja ładowania" + }, + "#": "no-question-hint-check", "tagRenderings": [ "images", { @@ -5132,6 +5026,113 @@ } } ], + "lineRendering": [], + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": "pin", + "color": "#fff" + }, + { + "icon": { + "render": "./assets/themes/charging_stations/plug.svg", + "mappings": [ + { + "if": "bicycle=yes", + "then": "./assets/themes/charging_stations/bicycle.svg" + }, + { + "if": { + "or": [ + "car=yes", + "motorcar=yes" + ] + }, + "then": "./assets/themes/charging_stations/car.svg" + } + ] + } + } + ], + "iconBadges": [ + { + "if": { + "or": [ + "disused:amenity=charging_station", + "operational_status=broken" + ] + }, + "then": "close:#c22;" + }, + { + "if": { + "or": [ + "proposed:amenity=charging_station", + "planned:amenity=charging_station" + ] + }, + "then": "./assets/layers/charging_station/under_construction.svg" + }, + { + "if": { + "and": [ + "bicycle=yes", + { + "or": [ + "motorcar=yes", + "car=yes" + ] + } + ] + }, + "then": "circle:#fff;./assets/themes/charging_stations/car.svg" + } + ], + "anchor": "bottom", + "iconSize": "50,50" + } + ], + "presets": [ + { + "tags": [ + "amenity=charging_station", + "motorcar=no", + "bicycle=yes", + "socket:typee=1" + ], + "title": { + "en": "a charging station for electrical bikes with a normal european wall plug (meant to charge electrical bikes)", + "nl": "een oplaadpunt voor elektrische fietsen met een gewoon Europees stopcontact (speciaal bedoeld voor fietsen)", + "ca": "una estació de càrrega per a bicicletes elèctriques amb un endoll de paret europeu normal (destinat a carregar bicicletes elèctriques)", + "cs": "nabíjecí stanice pro elektrokola s běžnou evropskou zástrčkou (určeno k nabíjení elektrických kol)", + "da": "en ladestation til elektriske cykler med et normalt europæisk vægstik (beregnet til opladning af elektriske cykler)", + "de": "eine Ladestation für Elektrofahrräder mit einer normalen europäischen Steckdose (zum Laden von Elektrofahrrädern)", + "es": "una estación de carga para bicicletas eléctricas con un enchufe de pared europeo normal (pensado para cargar bicicletas eléctricas)" + } + }, + { + "tags": [ + "amenity=charging_station", + "motorcar=yes", + "bicycle=no" + ], + "title": { + "en": "a charging station for cars", + "nl": "een oplaadstation voor elektrische auto's", + "ca": "una estació de càrrega per a cotxes", + "cs": "nabíjecí stanice pro automobily", + "da": "en ladestation til biler", + "de": "Eine Ladestation für Elektrofahrzeuge", + "es": "una estación de carga para coches", + "pl": "stacja ładowania dla samochodów" + } + } + ], "filter": [ { "id": "vehicle-type", @@ -5419,19 +5420,6 @@ ] } ], - "deletion": { - "softDeletionTags": { - "and": [ - "amenity=", - "disused:amenity=charging_station" - ] - }, - "neededChangesets": 10 - }, - "allowMove": { - "enableRelocation": false, - "enableImproveAccuracy": true - }, "units": [ { "appliesToKey": [ @@ -5681,5 +5669,17 @@ "eraseInvalidValues": true } ], - "#": "no-question-hint-check" -} + "allowMove": { + "enableRelocation": false, + "enableImproveAccuracy": true + }, + "deletion": { + "softDeletionTags": { + "and": [ + "amenity=", + "disused:amenity=charging_station" + ] + }, + "neededChangesets": 10 + } +} \ No newline at end of file diff --git a/assets/layers/crab_address/crab_address.json b/assets/layers/crab_address/crab_address.json index d9f56e0e8..86805c235 100644 --- a/assets/layers/crab_address/crab_address.json +++ b/assets/layers/crab_address/crab_address.json @@ -32,9 +32,7 @@ "tagRenderings": [ { "id": "render_crab", - "render": { - "nl": "Volgens het CRAB ligt hier {STRAATNM} {HUISNR} (label: {HNRLABEL})" - } + "render": "Volgens het CRAB ligt hier {STRAATNM} {HUISNR} (label: {HNRLABEL})" } ] } diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 33606f2da..8cc53cc9a 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -129,7 +129,7 @@ "then": "./assets/layers/id_presets/temaki-hammer_shoe.svg" } ], - "mappings+":[ + "mappings+": [ { "if": "craft=key_cutter", "then": "./assets/layers/id_presets/fas-key.svg" @@ -156,7 +156,7 @@ { "if": { "or": [ - "service:key_cutting=yes", + "service:key_cutting=yes", "craft=key_cutter" ] }, @@ -399,7 +399,7 @@ { "id": "key_cutter", "question": { - "en":"Does this shop offer key cutting?" + "en": "Does this shop offer key cutting?" }, "mappings": [ { diff --git a/assets/layers/slow_roads/slow_roads.json b/assets/layers/slow_roads/slow_roads.json index 3b617b0a3..ccb9e7e03 100644 --- a/assets/layers/slow_roads/slow_roads.json +++ b/assets/layers/slow_roads/slow_roads.json @@ -1,6 +1,7 @@ { "id": "slow_roads", "name": { + "en": "Paths, carfree and slow roads", "nl": "Paadjes, trage wegen en autoluwe straten" }, "description": { @@ -30,42 +31,51 @@ "minzoom": 16, "title": { "render": { + "en": "Slow road", "nl": "Trage weg" }, "mappings": [ { "if": "name~*", "then": { + "*": "{name}", "nl": "{name}" } }, { "if": "highway=footway", "then": { + "en": "Footway", "nl": "Voetpad" } }, { "if": "highway=cycleway", "then": { + "en": "Cycleway", "nl": "Fietspad" } }, { "if": "highway=pedestrian", "then": { + "en": "Pedestrian street", "nl": "Voetgangersstraat" } }, { "if": "highway=living_street", "then": { + "en": "Living street", "nl": "Woonerf" } }, { "if": "highway=path", - "then": "Klein pad" + "then": { + "en": "Small path", + "nl": "Klein pad" + } } ] }, @@ -122,7 +132,8 @@ { "if": "highway=living_street", "then": { - "nl:": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
" + "en": "This is a living street", + "nl": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
" }, "icon": { "path": "./assets/layers/slow_roads/woonerf.svg", @@ -132,30 +143,35 @@ { "if": "highway=pedestrian", "then": { + "en": "This is a wide, carfree street", "nl": "Dit is een brede, autovrije straat" } }, { "if": "highway=footway", "then": { + "en": "This is a footway", "nl": "Dit is een voetpaadje" } }, { "if": "highway=path", "then": { + "en": "This is a small path", "nl": "Dit is een wegeltje of bospad" } }, { "if": "highway=bridleway", "then": { + "en": "This is a bridleway", "nl": "Dit is een ruiterswegel" } }, { "if": "highway=track", "then": { + "en": "This is a land access road", "nl": "Dit is een tractorspoor of weg om landbouwgrond te bereikken" } } @@ -163,6 +179,7 @@ }, { "question": { + "en": "What surface does this road have?", "nl": "Wat is de wegverharding van dit pad?" }, "render": { @@ -299,7 +316,10 @@ }, { "id": "slow_road_is_lit", - "question": "Is deze weg 's nachts verlicht?", + "question": { + "en": "Is this road lit at night?", + "nl": "Is deze weg 's nachts verlicht?" + }, "mappings": [ { "if": "lit=yes", @@ -307,7 +327,10 @@ }, { "if": "lit=no", - "then": "Niet verlicht" + "then": { + "en": "Not lit", + "nl": "Niet verlicht" + } } ] } diff --git a/assets/layers/street_lamps/street_lamps.json b/assets/layers/street_lamps/street_lamps.json index 673064166..0f11997ad 100644 --- a/assets/layers/street_lamps/street_lamps.json +++ b/assets/layers/street_lamps/street_lamps.json @@ -461,7 +461,6 @@ "en": "This lamp has 1 fixture", "nl": "Deze lantaarn heeft 1 lamp", "de": "Diese Straßenlaterne hat 1 Leuchte", - "es": "", "ca": "Aquest fanal té 1 aparell", "cs": "Tato lampa má 1 světlo" } diff --git a/assets/layers/trail/trail.json b/assets/layers/trail/trail.json index 50972d578..e6bacfdf3 100644 --- a/assets/layers/trail/trail.json +++ b/assets/layers/trail/trail.json @@ -11,6 +11,7 @@ "cs": "Stezky" }, "description": { + "en": "Waymarked trails", "nl": "Aangeduide wandeltochten" }, "source": { @@ -103,9 +104,11 @@ }, { "question": { + "en": "What is the name of this trail?", "nl": "Wat is de naam van deze wandeling?" }, "render": { + "en": "This trail is called {name}", "nl": "Deze wandeling heet {name}" }, "freeform": { @@ -115,9 +118,11 @@ }, { "render": { + "en": "This trail is maintained by {operator}", "nl": "Beheer door {operator}" }, "question": { + "en": "Who maintains this trail?", "nl": "Wie beheert deze wandeltocht?" }, "freeform": { @@ -131,6 +136,7 @@ ] }, "then": { + "en": "This trail is maintained by Natuurpunt", "nl": "Dit gebied wordt beheerd door Natuurpunt" }, "icon": { @@ -145,6 +151,7 @@ ] }, "then": { + "en": "This trail is maintained by {operator}", "nl": "Dit gebied wordt beheerd door {operator}" }, "hideInAnswer": true, @@ -158,9 +165,11 @@ }, { "question": { + "en": "What is the reference colour of this trail?", "nl": "Welke kleur heeft deze wandeling?" }, "render": { + "en": "The reference colour is {colour}", "nl": "Deze wandeling heeft kleur {colour}" }, "freeform": { @@ -221,17 +230,20 @@ }, { "question": { + "en": "Is this trail wheelchair accessible?", "nl": "Is deze wandeling toegankelijk met de rolstoel?" }, "mappings": [ { "then": { + "en": "This trail is wheelchair-accessible", "nl": "deze wandeltocht is toegankelijk met de rolstoel" }, "if": "wheelchair=yes" }, { "then": { + "en": "This trail is not wheelchair accessible", "nl": "deze wandeltocht is niet toegankelijk met de rolstoel" }, "if": "wheelchair=no" @@ -241,17 +253,20 @@ }, { "question": { + "en": "Is this trail accessible with a pushchair?", "nl": "Is deze wandeltocht toegankelijk met de buggy?" }, "mappings": [ { "then": { + "en": "This trail is accessible with a pushchair", "nl": "deze wandeltocht is toegankelijk met de buggy" }, "if": "pushchair=yes" }, { "then": { + "en": "This trail is not accessible with a pushchair", "nl": "deze wandeltocht is niet toegankelijk met de buggy" }, "if": "pushchair=no" diff --git a/langs/layers/en.json b/langs/layers/en.json index fbfd5ac04..bed9a57f5 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -8523,7 +8523,8 @@ "2": { "then": "This shops does not offer key cutting as a service" } - } + }, + "question": "Does this shop offer key cutting?" }, "organic": { "mappings": { @@ -8631,7 +8632,38 @@ }, "slow_roads": { "description": "All carfree roads", + "name": "Paths, carfree and slow roads", "tagRenderings": { + "explanation": { + "mappings": { + "0": { + "then": "This is a living street" + }, + "1": { + "then": "This is a wide, carfree street" + }, + "2": { + "then": "This is a footway" + }, + "3": { + "then": "This is a small path" + }, + "4": { + "then": "This is a bridleway" + }, + "5": { + "then": "This is a land access road" + } + } + }, + "slow_road_is_lit": { + "mappings": { + "1": { + "then": "Not lit" + } + }, + "question": "Is this road lit at night?" + }, "slow_roads-surface": { "mappings": { "0": { @@ -8659,8 +8691,29 @@ "then": "The surface is paved" } }, + "question": "What surface does this road have?", "render": "The surface is {surface}" } + }, + "title": { + "mappings": { + "1": { + "then": "Footway" + }, + "2": { + "then": "Cycleway" + }, + "3": { + "then": "Pedestrian street" + }, + "4": { + "then": "Living street" + }, + "5": { + "then": "Small path" + } + }, + "render": "Slow road" } }, "speed_camera": { @@ -9696,6 +9749,7 @@ } }, "trail": { + "description": "Waymarked trails", "name": "Trails", "tagRenderings": { "Color": { @@ -9712,7 +9766,47 @@ "3": { "then": "Yellow trail" } - } + }, + "question": "What is the reference colour of this trail?", + "render": "The reference colour is {colour}" + }, + "Name": { + "question": "What is the name of this trail?", + "render": "This trail is called {name}" + }, + "Operator tag": { + "mappings": { + "0": { + "then": "This trail is maintained by Natuurpunt" + }, + "1": { + "then": "This trail is maintained by {operator}" + } + }, + "question": "Who maintains this trail?", + "render": "This trail is maintained by {operator}" + }, + "Wheelchair access": { + "mappings": { + "0": { + "then": "This trail is wheelchair-accessible" + }, + "1": { + "then": "This trail is not wheelchair accessible" + } + }, + "question": "Is this trail wheelchair accessible?" + }, + "pushchair access": { + "mappings": { + "0": { + "then": "This trail is accessible with a pushchair" + }, + "1": { + "then": "This trail is not accessible with a pushchair" + } + }, + "question": "Is this trail accessible with a pushchair?" }, "trail-length": { "render": "The trail is {_length:km} kilometers long" diff --git a/langs/layers/es.json b/langs/layers/es.json index 9b181702a..6f9104781 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -4246,11 +4246,6 @@ "question": "¿De qué color es la luz que emite esta lámpara?", "render": "Esta lámpara emite luz {light:colour}" }, - "count": { - "mappings": { - "0": {} - } - }, "direction": { "question": "¿Hacia donde apunta esta lámpara?", "render": "Esta lámpara apunta hacia {light:direction}" diff --git a/langs/layers/hu.json b/langs/layers/hu.json index 821a865ec..385cc8dc4 100644 --- a/langs/layers/hu.json +++ b/langs/layers/hu.json @@ -340,9 +340,6 @@ "description": "Létesítmény, ahonnan kerékpár kölcsönözhető hosszabb időre", "tagRenderings": { "bicycle-library-target-group": { - "mappings": { - "0": {} - }, "question": "Ki kölcsönözhet itt kerékpárt?" }, "bicycle_library-charge": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index e04878e28..35f8706f0 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -3272,13 +3272,6 @@ "render": "Klok" } }, - "crab_address": { - "tagRenderings": { - "render_crab": { - "render": "Volgens het CRAB ligt hier {STRAATNM} {HUISNR} (label: {HNRLABEL})" - } - } - }, "crossings": { "description": "Oversteekplaatsen voor voetgangers en fietsers", "name": "Oversteekplaatsen", @@ -7631,6 +7624,9 @@ "tagRenderings": { "explanation": { "mappings": { + "0": { + "then": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
" + }, "1": { "then": "Dit is een brede, autovrije straat" }, @@ -7648,6 +7644,14 @@ } } }, + "slow_road_is_lit": { + "mappings": { + "1": { + "then": "Niet verlicht" + } + }, + "question": "Is deze weg 's nachts verlicht?" + }, "slow_roads-surface": { "mappings": { "0": { @@ -7695,6 +7699,9 @@ }, "4": { "then": "Woonerf" + }, + "5": { + "then": "Klein pad" } }, "render": "Trage weg" diff --git a/package.json b/package.json index badd23d34..f26df1c61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.34.0", + "version": "0.34.1", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", @@ -74,7 +74,7 @@ "lint:prettier": "prettier --check '**/*.ts' '**/*.svelte'", "format": "prettier --write '**/*.ts' '**/*.svelte'", "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", - "clean": "rm -rf .cache/ && (find *.html | grep -v \"^\\(404\\|index\\|land\\|test\\|studio\\|theme\\|style_test\\|statistics\\|leaderboard).html\" | xargs -r rm) && (ls | grep \"^index_[a-zA-Z_-]\\+\\.ts$\" | xargs -r rm)", + "clean": "rm -rf .cache/ && (find *.html | grep -v \"^\\(404\\|index\\|land\\|test\\|studio\\|theme\\|style_test\\|statistics\\|leaderboard\\).html\" | xargs -r rm) && (ls | grep \"^index_[a-zA-Z_-]\\+\\.ts$\" | xargs -r rm)", "generate:dependency-graph": "node_modules/.bin/depcruise --exclude \"^node_modules\" --output-type dot Logic/State/MapState.ts > dependencies.dot && dot dependencies.dot -T svg -o dependencies.svg && rm dependencies.dot", "weblate-add-upstream": "git remote add weblate-github git@github.com:weblate/MapComplete.git && git remote add weblate-hosted-core https://hosted.weblate.org/git/mapcomplete/core/ && git remote add weblate-hosted-layers https://hosted.weblate.org/git/mapcomplete/layers/", "weblate-merge": "git remote update weblate-github; git merge weblate-github/weblate-mapcomplete-core weblate-github/weblate-mapcomplete-layers weblate-github/weblate-mapcomplete-layer-translations", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 95311bd42..c5eafe5a6 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -946,6 +946,10 @@ video { margin-right: 0.75rem; } +.mt-16 { + margin-top: 4rem; +} + .mr-12 { margin-right: 3rem; } @@ -1149,6 +1153,10 @@ video { max-height: 100vh; } +.max-h-60 { + max-height: 15rem; +} + .w-full { width: 100%; } diff --git a/scripts/build.sh b/scripts/build.sh index 2d75f21d2..3c3cc385b 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -28,8 +28,8 @@ BRANCH=`git rev-parse --abbrev-ref HEAD` echo "The branch name is $BRANCH" if [ $BRANCH = "develop" ] then - # SRC_MAPS="--sourcemap" - echo "Source maps are NOT enabled as they consume to much RAM" + SRC_MAPS="--sourcemap" + echo "Source maps are enabled " fi if [ $BRANCH = "master" ] || [ $BRANCH = "develop" ] diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 0f19129c8..4ffcee206 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -16,7 +16,6 @@ import { PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" import { PrepareTheme } from "../src/Models/ThemeConfig/Conversion/PrepareTheme" import { Conversion, - ConversionContext, DesugaringContext, DesugaringStep, } from "../src/Models/ThemeConfig/Conversion/Conversion" @@ -30,6 +29,7 @@ import LayerConfig from "../src/Models/ThemeConfig/LayerConfig" import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig" import { ConfigMeta } from "../src/UI/Studio/configMeta" +import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext" // 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 @@ -767,7 +767,7 @@ class LayerOverviewUtils extends Script { t.shortDescription ?? new Translation(t.description) .FirstSentence() - .OnEveryLanguage((s) => parse_html(s).innerText).translations, + .OnEveryLanguage((s) => parse_html(s).textContent).translations, mustHaveLanguage: t.mustHaveLanguage?.length > 0, } }) diff --git a/scripts/hetzner/deployHetzner.sh b/scripts/hetzner/deployHetzner.sh index 8498129bd..17042bfce 100755 --- a/scripts/hetzner/deployHetzner.sh +++ b/scripts/hetzner/deployHetzner.sh @@ -12,8 +12,8 @@ cp config.json config.json.bu && cp ./scripts/hetzner/config.json . && # Copy the config _before_ building, as the config might contain some needed URLs # npm run reset:layeroverview +# npm run test && npm run prepare-deploy && -npm run test && zip dist.zip -r dist/* && mv config.json.bu config.json && scp ./scripts/hetzner/config/* hetzner:/root/ && diff --git a/scripts/lint.ts b/scripts/lint.ts index 06db62a47..09206e68c 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -7,12 +7,12 @@ import { import Translations from "../src/UI/i18n/Translations" import { Translation } from "../src/UI/i18n/Translation" import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" -import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/Conversion" import themeconfig from "../src/assets/schemas/layoutconfigmeta.json" import layerconfig from "../src/assets/schemas/layerconfigmeta.json" import { Utils } from "../src/Utils" import { ConfigMeta } from "../src/UI/Studio/configMeta" +import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext" /* * This script reads all theme and layer files and reformats them inplace diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 8adb02e0f..f33f2df04 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -118,13 +118,10 @@ export class OsmConnection { if (options.oauth_token?.data !== undefined) { console.log(options.oauth_token.data) const self = this - this.auth.bootstrapToken( - options.oauth_token.data, - (err, result) => { - console.log("Bootstrap token called back", err, result) - self.AttemptLogin() - } - ) + this.auth.bootstrapToken(options.oauth_token.data, (err, result) => { + console.log("Bootstrap token called back", err, result) + self.AttemptLogin() + }) options.oauth_token.setData(undefined) } @@ -281,20 +278,24 @@ export class OsmConnection { content?: string, allowAnonymous: boolean = false ): Promise { - let connection: OSMAuthInstance = this.auth - if(allowAnonymous && !this.auth.authenticated()) { - const possibleResult = await Utils.downloadAdvanced(`${this.Backend()}/api/0.6/${path}`,header, method, content) - if(possibleResult["content"]) { + if (allowAnonymous && !this.auth.authenticated()) { + const possibleResult = await Utils.downloadAdvanced( + `${this.Backend()}/api/0.6/${path}`, + header, + method, + content + ) + if (possibleResult["content"]) { return possibleResult["content"] } console.error(possibleResult) - throw "Could not interact with OSM:"+possibleResult["error"] + throw "Could not interact with OSM:" + possibleResult["error"] } return new Promise((ok, error) => { connection.xhr( - { + { method, options: { header, @@ -330,8 +331,12 @@ export class OsmConnection { return await this.interact(path, "PUT", header, content) } - public async get(path: string, header?: Record): Promise { - return await this.interact(path, "GET", header) + public async get( + path: string, + header?: Record, + allowAnonymous: boolean = false + ): Promise { + return await this.interact(path, "GET", header, undefined, allowAnonymous) } public closeNote(id: number | string, text?: string): Promise { @@ -374,9 +379,14 @@ export class OsmConnection { } // Lat and lon must be strings for the API to accept it const content = `lat=${lat}&lon=${lon}&text=${encodeURIComponent(text)}` - const response = await this.post("notes.json", content, { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" - }, true) + const response = await this.post( + "notes.json", + content, + { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + }, + true + ) const parsed = JSON.parse(response) console.log("Got result:", parsed) const id = parsed.properties @@ -519,7 +529,6 @@ export class OsmConnection { singlepage: !standalone, auto: true, }) - } private CheckForMessagesContinuously() { @@ -543,6 +552,29 @@ export class OsmConnection { }) } + private readonly _userInfoCache: Record = {} + public async getInformationAboutUser(id: number): Promise<{ + id: number + display_name: string + account_created: string + description: string + contributor_terms: { agreed: boolean } + roles: [] + changesets: { count: number } + traces: { count: number } + blocks: { received: { count: number; active: number } } + }> { + if (id === undefined) { + return undefined + } + if (this._userInfoCache[id]) { + return this._userInfoCache[id] + } + const info = await this.get("user/" + id + ".json", { accepts: "application/json" }, true) + const parsed = JSON.parse(info)["user"] + this._userInfoCache[id] = parsed + return parsed + } private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { if (Utils.runningFromConsole) { return { api: "online", gpx: "online" } diff --git a/src/Logic/Tags/Tag.ts b/src/Logic/Tags/Tag.ts index 198d97dd2..5142c5c62 100644 --- a/src/Logic/Tags/Tag.ts +++ b/src/Logic/Tags/Tag.ts @@ -79,6 +79,9 @@ export class Tag extends TagsFilter { currentProperties?: Record ) { let v = this.value + if (typeof v !== "string") { + v = JSON.stringify(v) + } if (shorten) { v = Utils.EllipsesAfter(v, 25) } diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index dc35a2573..adc862922 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -9,6 +9,8 @@ import { Or } from "./Or" import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" import key_counts from "../../assets/key_totals.json" +import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext" + type Tags = Record export type UploadableTag = Tag | SubstitutingTag | And @@ -475,12 +477,18 @@ export class TagUtils { * regex.matchesProperties({maxspeed: "50 mph"}) // => true */ - public static Tag(json: TagConfigJson, context: string = ""): TagsFilter { + public static Tag(json: TagConfigJson, context: string | ConversionContext = ""): TagsFilter { try { - return this.ParseTagUnsafe(json, context) + let ctx = typeof context === "string" ? context : context.path.join(".") + return this.ParseTagUnsafe(json, ctx) } catch (e) { - console.error("Could not parse tag", json, "in context", context, "due to ", e) - throw e + if (typeof context === "string") { + console.error("Could not parse tag", json, "in context", context, "due to ", e) + throw e + } else { + context.err(e) + return undefined + } } } diff --git a/src/Models/ThemeConfig/Conversion/AddContextToTranslations.ts b/src/Models/ThemeConfig/Conversion/AddContextToTranslations.ts index f33e75999..adbea661f 100644 --- a/src/Models/ThemeConfig/Conversion/AddContextToTranslations.ts +++ b/src/Models/ThemeConfig/Conversion/AddContextToTranslations.ts @@ -1,6 +1,7 @@ -import { ConversionContext, DesugaringStep } from "./Conversion" +import { DesugaringStep } from "./Conversion" import { Utils } from "../../../Utils" import Translations from "../../../UI/i18n/Translations" +import { ConversionContext } from "./ConversionContext" export class AddContextToTranslations extends DesugaringStep { private readonly _prefix: string diff --git a/src/Models/ThemeConfig/Conversion/Conversion.ts b/src/Models/ThemeConfig/Conversion/Conversion.ts index 5ad3f12c1..f2ebd427c 100644 --- a/src/Models/ThemeConfig/Conversion/Conversion.ts +++ b/src/Models/ThemeConfig/Conversion/Conversion.ts @@ -1,6 +1,7 @@ import { LayerConfigJson } from "../Json/LayerConfigJson" import { Utils } from "../../../Utils" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" +import { ConversionContext } from "./ConversionContext" export interface DesugaringContext { tagRenderings: Map @@ -8,112 +9,6 @@ export interface DesugaringContext { publicLayers?: Set } -export class ConversionContext { - /** - * The path within the data structure where we are currently operating - */ - readonly path: ReadonlyArray - /** - * Some information about the current operation - */ - readonly operation: ReadonlyArray - readonly messages: ConversionMessage[] - - private constructor( - messages: ConversionMessage[], - path: ReadonlyArray, - operation?: ReadonlyArray - ) { - this.path = path - this.operation = operation ?? [] - // Messages is shared by reference amonst all 'context'-objects for performance - this.messages = messages - if (this.path.some((p) => typeof p === "object" || p === "[object Object]")) { - throw "ConversionMessage: got an object as path entry:" + JSON.stringify(path) - } - } - - public static construct(path: (string | number)[], operation: string[]) { - return new ConversionContext([], [...path], [...operation]) - } - - public static test(msg?: string) { - return new ConversionContext([], msg ? [msg] : [], ["test"]) - } - - static print(msg: ConversionMessage) { - const noString = msg.context.path.filter( - (p) => typeof p !== "string" && typeof p !== "number" - ) - if (noString.length > 0) { - console.warn("Non-string value in path:", ...noString) - } - 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) - } - } - - 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.messages, [...this.path, key], this.operation) - } - return new ConversionContext(this.messages, [...this.path, ...key], this.operation) - } - - public enters(...key: (string | number)[]) { - return this.enter(key) - } - - public inOperation(key: string) { - return new ConversionContext(this.messages, 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 }) - } - - getAll(mode: ConversionMsgLevel): ConversionMessage[] { - return this.messages.filter((m) => m.level === mode) - } - public hasErrors() { - return this.messages?.find((m) => m.level === "error") !== undefined - } - - debug(message: string) { - this.messages.push({ context: this, level: "debug", message }) - } -} - export type ConversionMsgLevel = "debug" | "information" | "warning" | "error" export interface ConversionMessage { context: ConversionContext diff --git a/src/Models/ThemeConfig/Conversion/ConversionContext.ts b/src/Models/ThemeConfig/Conversion/ConversionContext.ts new file mode 100644 index 000000000..88fdcb771 --- /dev/null +++ b/src/Models/ThemeConfig/Conversion/ConversionContext.ts @@ -0,0 +1,116 @@ +import { ConversionMessage, ConversionMsgLevel } from "./Conversion" + +export class ConversionContext { + /** + * The path within the data structure where we are currently operating + */ + readonly path: ReadonlyArray + /** + * Some information about the current operation + */ + readonly operation: ReadonlyArray + readonly messages: ConversionMessage[] + + private _hasErrors: boolean = false + + private constructor( + messages: ConversionMessage[], + path: ReadonlyArray, + operation?: ReadonlyArray + ) { + this.path = path + this.operation = operation ?? [] + // Messages is shared by reference amonst all 'context'-objects for performance + this.messages = messages + if (this.path.some((p) => typeof p === "object" || p === "[object Object]")) { + throw "ConversionMessage: got an object as path entry:" + JSON.stringify(path) + } + } + + public static construct(path: (string | number)[], operation: string[]) { + return new ConversionContext([], [...path], [...operation]) + } + + public static test(msg?: string) { + return new ConversionContext([], msg ? [msg] : [], ["test"]) + } + + static print(msg: ConversionMessage) { + const noString = msg.context.path.filter( + (p) => typeof p !== "string" && typeof p !== "number" + ) + if (noString.length > 0) { + console.warn("Non-string value in path:", ...noString) + } + 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) + } + } + + 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.messages, [...this.path, key], this.operation) + } + return new ConversionContext(this.messages, [...this.path, ...key], this.operation) + } + + public enters(...key: (string | number)[]) { + return this.enter(key) + } + + public inOperation(key: string) { + return new ConversionContext(this.messages, this.path, [...this.operation, key]) + } + + warn(message: string) { + this.messages.push({ context: this, level: "warning", message }) + } + + err(message: string) { + this._hasErrors = true + this.messages.push({ context: this, level: "error", message }) + } + + info(message: string) { + this.messages.push({ context: this, level: "information", message }) + } + + getAll(mode: ConversionMsgLevel): ConversionMessage[] { + return this.messages.filter((m) => m.level === mode) + } + + public hasErrors() { + if (this._hasErrors) { + return true + } + const foundErr = this.messages?.find((m) => m.level === "error") !== undefined + this._hasErrors = foundErr + return foundErr + } + + debug(message: string) { + this.messages.push({ context: this, level: "debug", message }) + } +} diff --git a/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts b/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts index 26d29811d..c88eb6541 100644 --- a/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts +++ b/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts @@ -1,8 +1,9 @@ -import { Conversion, ConversionContext } from "./Conversion" +import { Conversion } from "./Conversion" import LayerConfig from "../LayerConfig" import { LayerConfigJson } from "../Json/LayerConfigJson" import Translations from "../../../UI/i18n/Translations" import { Translation, TypedTranslation } from "../../../UI/i18n/Translation" +import { ConversionContext } from "./ConversionContext" export default class CreateNoteImportLayer extends Conversion { /** diff --git a/src/Models/ThemeConfig/Conversion/FixImages.ts b/src/Models/ThemeConfig/Conversion/FixImages.ts index 3d6eee958..6183bbafb 100644 --- a/src/Models/ThemeConfig/Conversion/FixImages.ts +++ b/src/Models/ThemeConfig/Conversion/FixImages.ts @@ -1,4 +1,4 @@ -import { Conversion, ConversionContext, DesugaringStep } from "./Conversion" +import { Conversion, DesugaringStep } from "./Conversion" import { LayoutConfigJson } from "../Json/LayoutConfigJson" import { Utils } from "../../../Utils" import metapaths from "../../../assets/schemas/layoutconfigmeta.json" @@ -6,6 +6,7 @@ import tagrenderingmetapaths from "../../../assets/schemas/questionabletagrender import Translations from "../../../UI/i18n/Translations" import { parse as parse_html } from "node-html-parser" +import { ConversionContext } from "./ConversionContext" export class ExtractImages extends Conversion< LayoutConfigJson, diff --git a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 780985e60..df4dec459 100644 --- a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -2,8 +2,9 @@ import { LayoutConfigJson } from "../Json/LayoutConfigJson" import { Utils } from "../../../Utils" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import { LayerConfigJson } from "../Json/LayerConfigJson" -import { ConversionContext, DesugaringStep, Each, Fuse, On } from "./Conversion" +import { DesugaringStep, Each, Fuse, On } from "./Conversion" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" +import { ConversionContext } from "./ConversionContext" export class UpdateLegacyLayer extends DesugaringStep< LayerConfigJson | string | { builtin; override } @@ -57,6 +58,9 @@ export class UpdateLegacyLayer extends DesugaringStep< if (config.tagRenderings !== undefined) { let i = 0 for (const tagRendering of config.tagRenderings) { + if (!tagRendering) { + continue + } i++ if ( typeof tagRendering === "string" || diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts index b75b6af2b..923fed502 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -1,8 +1,6 @@ import { - Cached, Concat, Conversion, - ConversionContext, DesugaringContext, DesugaringStep, Each, @@ -32,7 +30,7 @@ import { RenderingSpecification } from "../../../UI/SpecialVisualization" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConfigMeta } from "../../../UI/Studio/configMeta" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" -import { j } from "vite-node/types-63205a44" +import { ConversionContext } from "./ConversionContext" class ExpandFilter extends DesugaringStep { private static readonly predefinedFilters = ExpandFilter.load_filters() @@ -1192,9 +1190,9 @@ class ExpandMarkerRenderings extends DesugaringStep { } } -export class PrepareLayer extends Cached { +export class PrepareLayer extends Fuse { constructor(state: DesugaringContext) { - const steps = new Fuse( + super( "Fully prepares and expands a layer for the LayerConfig.", new On("tagRenderings", new Each(new RewriteSpecial())), new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), @@ -1224,6 +1222,5 @@ export class PrepareLayer extends Cached { ), new ExpandFilter(state) ) - super(steps) } } diff --git a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts index 930c1e907..2b995f757 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -1,7 +1,6 @@ import { Concat, Conversion, - ConversionContext, DesugaringContext, DesugaringStep, Each, @@ -21,6 +20,7 @@ import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import DependencyCalculator from "../DependencyCalculator" import { AddContextToTranslations } from "./AddContextToTranslations" import ValidationUtils from "./ValidationUtils" +import { ConversionContext } from "./ConversionContext" class SubstituteLayer extends Conversion { private readonly _state: DesugaringContext diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index cc85a78a4..771215b40 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -1,13 +1,4 @@ -import { - Conversion, - ConversionContext, - DesugaringStep, - Each, - Fuse, - On, - Pipe, - Pure, -} from "./Conversion" +import { Conversion, DesugaringStep, Each, Fuse, On, Pipe, Pure } from "./Conversion" import { LayerConfigJson } from "../Json/LayerConfigJson" import LayerConfig from "../LayerConfig" import { Utils } from "../../../Utils" @@ -29,6 +20,8 @@ import TagRenderingConfig from "../TagRenderingConfig" import { parse as parse_html } from "node-html-parser" import PresetConfig from "../PresetConfig" import { TagsFilter } from "../../../Logic/Tags/TagsFilter" +import { Translatable } from "../Json/Translatable" +import { ConversionContext } from "./ConversionContext" class ValidateLanguageCompleteness extends DesugaringStep { private readonly _languages: string[] @@ -285,7 +278,7 @@ export class ValidateThemeAndLayers extends Fuse { new Each( new Pipe( new ValidateLayer(undefined, isBuiltin, doesImageExist, false, true), - new Pure((x) => x.raw) + new Pure((x) => x?.raw) ) ) ) @@ -375,32 +368,34 @@ export class DetectConflictingAddExtraTags extends DesugaringStep t.key) + + const duplicateKeys = keysInAddExtraTags.filter((k) => keysInMapping.has(k)) + if (duplicateKeys.length > 0) { + context + .enters("mappings", i) + .err( + "AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + + duplicateKeys.join(", ") + ) + } } - const keysInMapping = new Set(mapping.if.usedKeys()) - const keysInAddExtraTags = mapping.addExtraTags.map((t) => t.key) - - const duplicateKeys = keysInAddExtraTags.filter((k) => keysInMapping.has(k)) - if (duplicateKeys.length > 0) { - errors.push( - "At " + - context + - ".mappings[" + - i + - "]: AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + - duplicateKeys.join(", ") - ) - } + return json + } catch (e) { + context.err(e) + return undefined } - - return json } } @@ -475,8 +470,8 @@ export class DetectShadowedMappings extends DesugaringStep { - const ctx = `${context}.mappings[${i}]` - const ifTags = TagUtils.Tag(m.if, ctx) + const c = context.enters("mappings", i) + const ifTags = TagUtils.Tag(m.if, c.enter("if")) const hideInAnswer = m["hideInAnswer"] if (hideInAnswer !== undefined && hideInAnswer !== false && hideInAnswer !== true) { let conditionTags = TagUtils.Tag(hideInAnswer) @@ -486,7 +481,7 @@ export class DetectShadowedMappings extends DesugaringStep { - private _options: { noQuestionHintCheck: boolean } +class CheckTranslation extends DesugaringStep { + public static readonly allowUndefined: CheckTranslation = new CheckTranslation(true) + public static readonly noUndefined: CheckTranslation = new CheckTranslation() + private readonly _allowUndefined: boolean - constructor(options: { noQuestionHintCheck: boolean }) { + constructor(allowUndefined: boolean = false) { + super( + "Checks that a translation is valid and internally consistent", + ["*"], + "CheckTranslation" + ) + this._allowUndefined = allowUndefined + } + + convert(json: Translatable, context: ConversionContext): Translatable { + if (json === undefined || json === null) { + if (!this._allowUndefined) { + context.err("Expected a translation, but got " + json) + } + return json + } + if (typeof json === "string") { + return json + } + const keys = Object.keys(json) + if (keys.length === 0) { + context.err("No actual values are given in this translation, it is completely empty") + return json + } + const en = json["en"] + if (!en && json["*"] === undefined) { + const msg = "Received a translation without english version" + context.warn(msg) + } + + for (const key of keys) { + const lng = json[key] + if (lng === "") { + context.enter(lng).err("Got an empty string in translation for language " + lng) + } + + // TODO validate that all subparts are here + } + + return json + } +} + +class MiscTagRenderingChecks extends DesugaringStep { + constructor() { super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks") - this._options = options } convert( @@ -678,10 +718,42 @@ class MiscTagRenderingChecks extends DesugaringStep { 'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' ) } + + { + for (const key of ["question", "questionHint", "render"]) { + CheckTranslation.allowUndefined.convert(json[key], context.enter(key)) + } + for (let i = 0; i < json.mappings?.length ?? 0; i++) { + const mapping = json.mappings[i] + CheckTranslation.noUndefined.convert( + mapping.then, + context.enters("mappings", i, "then") + ) + if (!mapping.if) { + context.enters("mappings", i).err("No `if` is defined") + } + } + } if (json["group"]) { context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead') } + if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) { + context.err( + "A question is defined, but no mappings nor freeform (key) are. Add at least one of them" + ) + } + if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) { + context.err("A question is defined, but there is only one option to choose from.") + } + if (json["questionHint"] && !json["question"]) { + context + .enter("questionHint") + .err( + "A questionHint is defined, but no question is given. As such, the questionHint will never be shown" + ) + } + if (json.freeform) { if (json.render === undefined) { context @@ -771,16 +843,13 @@ class MiscTagRenderingChecks extends DesugaringStep { ) } } + return json } } export class ValidateTagRenderings extends Fuse { - constructor( - layerConfig?: LayerConfigJson, - doesImageExist?: DoesImageExist, - options?: { noQuestionHintCheck: boolean } - ) { + constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) { super( "Various validation on tagRenderingConfigs", new DetectShadowedMappings(layerConfig), @@ -790,67 +859,46 @@ export class ValidateTagRenderings extends Fuse { new On("question", new ValidatePossibleLinks()), new On("questionHint", new ValidatePossibleLinks()), new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), - new MiscTagRenderingChecks(options) + new MiscTagRenderingChecks() ) } } -export class ValidateLayer extends Conversion< - LayerConfigJson, - { parsed: LayerConfig; raw: LayerConfigJson } -> { - /** - * The paths where this layer is originally saved. Triggers some extra checks - * @private - */ - private readonly _path?: string +export class PrevalidateLayer extends DesugaringStep { private readonly _isBuiltin: boolean private readonly _doesImageExist: DoesImageExist + /** + * The paths where this layer is originally saved. Triggers some extra checks + */ + private readonly _path: string private readonly _studioValidations: boolean - private _skipDefaultLayers: boolean - constructor( - path: string, - isBuiltin: boolean, - doesImageExist: DoesImageExist, - studioValidations: boolean = false, - skipDefaultLayers: boolean = false - ) { - super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") + constructor(path: string, isBuiltin, doesImageExist, studioValidations) { + super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer") this._path = path this._isBuiltin = isBuiltin this._doesImageExist = doesImageExist this._studioValidations = studioValidations - this._skipDefaultLayers = skipDefaultLayers } - convert( - json: LayerConfigJson, - context: ConversionContext - ): { parsed: LayerConfig; raw: LayerConfigJson } { - context = context.inOperation(this.name) - if (typeof json === "string") { - context.err("This layer hasn't been expanded: " + json) - return null - } - - if (this._skipDefaultLayers && Constants.added_by_default.indexOf(json.id) >= 0) { - return { parsed: undefined, raw: json } - } - - if (typeof json === "string") { - context.err( - `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed` - ) - return undefined - } - + convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { if (json.id === undefined) { context.enter("id").err(`Not a valid layer: id is undefined`) + } else { + if (json.id?.toLowerCase() !== json.id) { + context.enter("id").err(`The id of a layer should be lowercase: ${json.id}`) + } + if (json.id?.match(/[a-z0-9-_]/) == null) { + context.enter("id").err(`The id of a layer should match [a-z0-9-_]*: ${json.id}`) + } } if (json.source === undefined) { - context.enter("source").err("No source section is defined") + context + .enter("source") + .err( + "No source section is defined; please define one as data is not loaded otherwise" + ) } else { if (json.source === "special" || json.source === "special:library") { } else if (json.source && json.source["osmTags"] === undefined) { @@ -884,13 +932,6 @@ export class ValidateLayer extends Conversion< } } - if (json.id?.toLowerCase() !== json.id) { - context.enter("id").err(`The id of a layer should be lowercase: ${json.id}`) - } - if (json.id?.match(/[a-z0-9-_]/) == null) { - context.enter("id").err(`The id of a layer should match [a-z0-9-_]*: ${json.id}`) - } - if ( json.syncSelection !== undefined && LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0 @@ -906,27 +947,6 @@ export class ValidateLayer extends Conversion< ) } - let layerConfig: LayerConfig - try { - layerConfig = new LayerConfig(json, "validation", true) - } catch (e) { - console.error(e) - context.err("Could not parse layer due to:" + e) - return undefined - } - for (let i = 0; i < (layerConfig.calculatedTags ?? []).length; i++) { - const [_, code, __] = layerConfig.calculatedTags[i] - try { - new Function("feat", "return " + code + ";") - } catch (e) { - context - .enters("calculatedTags", i) - .err( - `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}` - ) - } - } - if (json.source === "special") { if (!Constants.priviliged_layers.find((x) => x == json.id)) { context.err( @@ -937,6 +957,10 @@ export class ValidateLayer extends Conversion< } } + if (context.hasErrors()) { + return undefined + } + if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) { new On("tagRendering", new Each(new ValidateTagRenderings(json))) if (json.title === undefined && json.source !== "special:library") { @@ -1001,172 +1025,6 @@ export class ValidateLayer extends Conversion< } try { - if (this._isBuiltin) { - // Some checks for legacy elements - - if (json["overpassTags"] !== undefined) { - context.err( - "Layer " + - json.id + - 'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": }\' instead of "overpassTags": (note: this isn\'t your fault, the custom theme generator still spits out the old format)' - ) - } - const forbiddenTopLevel = [ - "icon", - "wayHandling", - "roamingRenderings", - "roamingRendering", - "label", - "width", - "color", - "colour", - "iconOverlays", - ] - for (const forbiddenKey of forbiddenTopLevel) { - if (json[forbiddenKey] !== undefined) - context.err( - "Layer " + json.id + " still has a forbidden key " + forbiddenKey - ) - } - if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { - context.err( - "Layer " + - json.id + - " contains an old 'hideUnderlayingFeaturesMinPercentage'" - ) - } - - if ( - json.isShown !== undefined && - (json.isShown["render"] !== undefined || json.isShown["mappings"] !== undefined) - ) { - context.warn("Has a tagRendering as `isShown`") - } - } - if (this._isBuiltin) { - // Check location of layer file - const expected: string = `assets/layers/${json.id}/${json.id}.json` - if (this._path != undefined && this._path.indexOf(expected) < 0) { - context.err( - "Layer is in an incorrect place. The path is " + - this._path + - ", but expected " + - expected - ) - } - } - if (this._isBuiltin) { - // Check for correct IDs - if (json.tagRenderings?.some((tr) => tr["id"] === "")) { - const emptyIndexes: number[] = [] - for (let i = 0; i < json.tagRenderings.length; i++) { - const tagRendering = json.tagRenderings[i] - if (tagRendering["id"] === "") { - emptyIndexes.push(i) - } - } - context - .enter(["tagRenderings", ...emptyIndexes]) - .err( - `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( - "," - )}])` - ) - } - - const duplicateIds = Utils.Duplicates( - (json.tagRenderings ?? []) - ?.map((f) => f["id"]) - .filter((id) => id !== "questions") - ) - if (duplicateIds.length > 0 && !Utils.runningFromConsole) { - context - .enter("tagRenderings") - .err(`Some tagRenderings have a duplicate id: ${duplicateIds}`) - } - - if (json.description === undefined) { - if (typeof json.source === null) { - context.err("A priviliged layer must have a description") - } else { - context.warn("A builtin layer should have a description") - } - } - } - - if (json.filter) { - new On("filter", new Each(new ValidateFilter())).convert(json, context) - } - - if (json.tagRenderings !== undefined) { - new On( - "tagRenderings", - new Each( - new ValidateTagRenderings(json, this._doesImageExist, { - noQuestionHintCheck: json["#"]?.indexOf("no-question-hint-check") >= 0, - }) - ) - ).convert(json, context) - } - - if (json.pointRendering !== null && json.pointRendering !== undefined) { - if (!Array.isArray(json.pointRendering)) { - throw ( - "pointRendering in " + - json.id + - " is not iterable, it is: " + - typeof json.pointRendering - ) - } - for (let i = 0; i < json.pointRendering.length; i++) { - const pointRendering = json.pointRendering[i] - if (pointRendering.marker === undefined) { - continue - } - for (const icon of pointRendering?.marker) { - const indexM = pointRendering?.marker.indexOf(icon) - if (!icon.icon) { - continue - } - if (icon.icon["condition"]) { - context - .enters("pointRendering", i, "marker", indexM, "icon", "condition") - .err( - "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead." - ) - } - } - } - } - - if (json.presets !== undefined) { - if (typeof json.source === "string") { - context.err("A special layer cannot have presets") - } - // Check that a preset will be picked up by the layer itself - const baseTags = TagUtils.Tag(json.source["osmTags"]) - for (let i = 0; i < json.presets.length; i++) { - const preset = json.presets[i] - const tags: { k: string; v: string }[] = new And( - preset.tags.map((t) => TagUtils.Tag(t)) - ).asChange({ id: "node/-1" }) - const properties = {} - for (const tag of tags) { - properties[tag.k] = tag.v - } - const doMatch = baseTags.matchesProperties(properties) - if (!doMatch) { - context - .enters("presets", i, "tags") - .err( - "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + - JSON.stringify(properties) + - "\n The required tags are: " + - baseTags.asHumanString(false, false, {}) - ) - } - } - } } catch (e) { context.err("Could not validate layer due to: " + e + e.stack) } @@ -1180,6 +1038,232 @@ export class ValidateLayer extends Conversion< } } + if (this._isBuiltin) { + // Some checks for legacy elements + + if (json["overpassTags"] !== undefined) { + context.err( + "Layer " + + json.id + + 'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": }\' instead of "overpassTags": (note: this isn\'t your fault, the custom theme generator still spits out the old format)' + ) + } + const forbiddenTopLevel = [ + "icon", + "wayHandling", + "roamingRenderings", + "roamingRendering", + "label", + "width", + "color", + "colour", + "iconOverlays", + ] + for (const forbiddenKey of forbiddenTopLevel) { + if (json[forbiddenKey] !== undefined) + context.err("Layer " + json.id + " still has a forbidden key " + forbiddenKey) + } + if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { + context.err( + "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" + ) + } + + if ( + json.isShown !== undefined && + (json.isShown["render"] !== undefined || json.isShown["mappings"] !== undefined) + ) { + context.warn("Has a tagRendering as `isShown`") + } + } + if (this._isBuiltin) { + // Check location of layer file + const expected: string = `assets/layers/${json.id}/${json.id}.json` + if (this._path != undefined && this._path.indexOf(expected) < 0) { + context.err( + "Layer is in an incorrect place. The path is " + + this._path + + ", but expected " + + expected + ) + } + } + if (this._isBuiltin) { + // Check for correct IDs + if (json.tagRenderings?.some((tr) => tr["id"] === "")) { + const emptyIndexes: number[] = [] + for (let i = 0; i < json.tagRenderings.length; i++) { + const tagRendering = json.tagRenderings[i] + if (tagRendering["id"] === "") { + emptyIndexes.push(i) + } + } + context + .enter(["tagRenderings", ...emptyIndexes]) + .err( + `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( + "," + )}])` + ) + } + + const duplicateIds = Utils.Duplicates( + (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions") + ) + if (duplicateIds.length > 0 && !Utils.runningFromConsole) { + context + .enter("tagRenderings") + .err(`Some tagRenderings have a duplicate id: ${duplicateIds}`) + } + + if (json.description === undefined) { + if (typeof json.source === null) { + context.err("A priviliged layer must have a description") + } else { + context.warn("A builtin layer should have a description") + } + } + } + + if (json.filter) { + new On("filter", new Each(new ValidateFilter())).convert(json, context) + } + + if (json.tagRenderings !== undefined) { + new On( + "tagRenderings", + new Each(new ValidateTagRenderings(json, this._doesImageExist)) + ).convert(json, context) + } + + if (json.pointRendering !== null && json.pointRendering !== undefined) { + if (!Array.isArray(json.pointRendering)) { + throw ( + "pointRendering in " + + json.id + + " is not iterable, it is: " + + typeof json.pointRendering + ) + } + for (let i = 0; i < json.pointRendering.length; i++) { + const pointRendering = json.pointRendering[i] + if (pointRendering.marker === undefined) { + continue + } + for (const icon of pointRendering?.marker) { + const indexM = pointRendering?.marker.indexOf(icon) + if (!icon.icon) { + continue + } + if (icon.icon["condition"]) { + context + .enters("pointRendering", i, "marker", indexM, "icon", "condition") + .err( + "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead." + ) + } + } + } + } + + if (json.presets !== undefined) { + if (typeof json.source === "string") { + context.err("A special layer cannot have presets") + } + // Check that a preset will be picked up by the layer itself + const baseTags = TagUtils.Tag(json.source["osmTags"]) + for (let i = 0; i < json.presets.length; i++) { + const preset = json.presets[i] + const tags: { k: string; v: string }[] = new And( + preset.tags.map((t) => TagUtils.Tag(t)) + ).asChange({ id: "node/-1" }) + const properties = {} + for (const tag of tags) { + properties[tag.k] = tag.v + } + const doMatch = baseTags.matchesProperties(properties) + if (!doMatch) { + context + .enters("presets", i, "tags") + .err( + "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + + JSON.stringify(properties) + + "\n The required tags are: " + + baseTags.asHumanString(false, false, {}) + ) + } + } + } + return json + } +} + +export class ValidateLayer extends Conversion< + LayerConfigJson, + { parsed: LayerConfig; raw: LayerConfigJson } +> { + private readonly _skipDefaultLayers: boolean + private readonly _prevalidation: PrevalidateLayer + + constructor( + path: string, + isBuiltin: boolean, + doesImageExist: DoesImageExist, + studioValidations: boolean = false, + skipDefaultLayers: boolean = false + ) { + super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") + this._prevalidation = new PrevalidateLayer( + path, + isBuiltin, + doesImageExist, + studioValidations + ) + this._skipDefaultLayers = skipDefaultLayers + } + + convert( + json: LayerConfigJson, + context: ConversionContext + ): { parsed: LayerConfig; raw: LayerConfigJson } { + context = context.inOperation(this.name) + if (typeof json === "string") { + context.err( + `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed` + ) + return undefined + } + + if (this._skipDefaultLayers && Constants.added_by_default.indexOf(json.id) >= 0) { + return { parsed: undefined, raw: json } + } + + this._prevalidation.convert(json, context.inOperation(this._prevalidation.name)) + + if (context.hasErrors()) { + return undefined + } + + let layerConfig: LayerConfig + try { + layerConfig = new LayerConfig(json, "validation", true) + } catch (e) { + context.err("Could not parse layer due to:" + e) + return undefined + } + for (let i = 0; i < (layerConfig.calculatedTags ?? []).length; i++) { + const [_, code, __] = layerConfig.calculatedTags[i] + try { + new Function("feat", "return " + code + ";") + } catch (e) { + context + .enters("calculatedTags", i) + .err( + `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}` + ) + } + } + return { raw: json, parsed: layerConfig } } } diff --git a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts index 6350b2b08..82c610ede 100644 --- a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts +++ b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts @@ -198,6 +198,8 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs freeform?: { /** * question: What is the name of the attribute that should be written to? + * This is the OpenStreetMap-key that that value will be written to + * * ifunset: do not offer a freeform textfield as answer option */ key: string @@ -215,7 +217,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs * A (translated) text that is shown (as gray text) within the textfield * type: translation */ - placeholder?: string | any + placeholder?: Translatable /** * Extra parameters to initialize the input helper arguments. @@ -259,7 +261,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs * * ifunset: This tagrendering will be shown if it is known, but cannot be edited by the contributor, effectively resutling in a read-only rendering */ - question?: string | Translatable + question?: Translatable /** * question: Should some extra information be shown to the contributor, alongside the question? @@ -267,7 +269,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs * This can give some extra information on what the answer should ook like * ifunset: No extra hint is given */ - questionHint?: string | Translatable + questionHint?: Translatable /** * A list of labels. These are strings that are used for various purposes, e.g. to only include a subset of the tagRenderings when reusing a layer diff --git a/src/Models/ThemeConfig/LayoutConfig.ts b/src/Models/ThemeConfig/LayoutConfig.ts index e5310d820..ce538d6b2 100644 --- a/src/Models/ThemeConfig/LayoutConfig.ts +++ b/src/Models/ThemeConfig/LayoutConfig.ts @@ -9,7 +9,8 @@ import { Utils } from "../../Utils" import LanguageUtils from "../../Utils/LanguageUtils" import { RasterLayerProperties } from "../RasterLayerProperties" -import { ConversionContext } from "./Conversion/Conversion" + +import { ConversionContext } from "./Conversion/ConversionContext" /** * Minimal information about a theme diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 1c4bd6140..3040fcf44 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -240,10 +240,6 @@ export default class TagRenderingConfig { ) } - if (this.question && this.freeform?.key === undefined && this.mappings === undefined) { - throw `${context}: A question is defined, but no mappings nor freeform (key) are. The question is ${this.question.txt} at ${context}` - } - if (!json.multiAnswer && this.mappings !== undefined && this.question !== undefined) { let keys = [] for (let i = 0; i < this.mappings.length; i++) { @@ -315,7 +311,7 @@ export default class TagRenderingConfig { ) { const ctx = `${translationKey}.mappings.${i}` if (mapping.if === undefined) { - throw `${ctx}: Invalid mapping: "if" is not defined in ${JSON.stringify(mapping)}` + throw `Invalid mapping: "if" is not defined` } if (mapping.then === undefined) { if (mapping["render"] !== undefined) { diff --git a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte index e839a5040..eaadc6f05 100644 --- a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -22,11 +22,14 @@ export let highlightedRendering: UIEventSource = undefined; export let showQuestionIfUnknown: boolean = false; - let editMode = false; + /** + * Indicates if this tagRendering currently shows the attribute or asks the question to _change_ the property + */ + export let editMode = !config.IsKnown(tags) || showQuestionIfUnknown; if (tags) { onDestroy( - tags.addCallbackAndRunD((tags) => { - editMode = showQuestionIfUnknown && !config.IsKnown(tags); + tags.addCallbackD((tags) => { + editMode = !config.IsKnown(tags) }) ); } diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index f6e414203..09cb8bad8 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -132,6 +132,7 @@ function onSave() { if (selectedTags === undefined) { + console.log("SelectedTags is undefined, ignoring 'onSave'-event") return; } if (layer === undefined || layer?.source === null) { @@ -197,20 +198,20 @@ - - {#if config.questionhint} -
- -
- {/if} + {#if config.questionhint} +
+ +
+ {/if} + {#if config.mappings?.length >= 8}
diff --git a/src/UI/Studio/ChooseLayerToEdit.svelte b/src/UI/Studio/ChooseLayerToEdit.svelte index 4aa3c0e01..4992bf63a 100644 --- a/src/UI/Studio/ChooseLayerToEdit.svelte +++ b/src/UI/Studio/ChooseLayerToEdit.svelte @@ -1,20 +1,10 @@ @@ -22,12 +12,7 @@
{#each Array.from(layerIds) as layer} - dispatch("layerSelected", layer)}> -
- -
- {layer.id} -
+ {/each}
{/if} diff --git a/src/UI/Studio/EditItemButton.svelte b/src/UI/Studio/EditItemButton.svelte new file mode 100644 index 000000000..bc06185e5 --- /dev/null +++ b/src/UI/Studio/EditItemButton.svelte @@ -0,0 +1,36 @@ + + + dispatch("layerSelected", info)}> +
+ +
+ {info.id} + {#if info.owner && info.owner !== $selfId} + (made by {$displayName ?? info.owner}) + {/if} +
diff --git a/src/UI/Studio/EditLayer.svelte b/src/UI/Studio/EditLayer.svelte index 93de221dc..0d27d8518 100644 --- a/src/UI/Studio/EditLayer.svelte +++ b/src/UI/Studio/EditLayer.svelte @@ -21,8 +21,8 @@ const layerSchema: ConfigMeta[] = layerSchemaRaw; export let state: EditLayerState; - const messages = state.messages; - const hasErrors = messages.map((m: ConversionMessage[]) => m.filter(m => m.level === "error").length); + let messages = state.messages; + let hasErrors = messages.mapD((m: ConversionMessage[]) => m.filter(m => m.level === "error").length); const configuration = state.configuration; const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group)); @@ -33,7 +33,7 @@ } - const title: Store = state.getStoreFor(["id"]); + let title: Store = state.getStoreFor(["id"]); const wl = window.location; const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="; @@ -53,13 +53,15 @@ let config = layerSchema.find(config => config.path.length === 1 && config.path[0] === id); config = Utils.Clone(config); config.required = true; - console.log(">>>", config); config.hints.ifunset = undefined; return config; } let requiredFields = ["id", "name", "description"]; let currentlyMissing = state.configuration.map(config => { + if(!config){ + return [] + } const missing = []; for (const requiredField of requiredFields) { if (!config[requiredField]) { @@ -160,7 +162,9 @@
{#if $highlightedItem !== undefined} highlightedItem.setData(undefined)}> - +
+ +
{/if} diff --git a/src/UI/Studio/EditLayerState.ts b/src/UI/Studio/EditLayerState.ts index e3f079512..4e8e4cbce 100644 --- a/src/UI/Studio/EditLayerState.ts +++ b/src/UI/Studio/EditLayerState.ts @@ -3,7 +3,6 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import { Conversion, - ConversionContext, ConversionMessage, DesugaringContext, Pipe, @@ -21,6 +20,7 @@ import { Feature, Point } from "geojson" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson" import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme" +import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext"; export interface HighlightedTagRendering { path: ReadonlyArray @@ -41,7 +41,9 @@ export abstract class EditJsonState { public readonly highlightedItem: UIEventSource = new UIEventSource( undefined ) + sendingUpdates = false private readonly _stores = new Map>() + private boolean constructor(schema: ConfigMeta[], server: StudioServer, category: "layers" | "themes") { this.schema = schema @@ -52,7 +54,13 @@ export abstract class EditJsonState { const layerId = this.getId() this.configuration - .mapD((config) => JSON.stringify(config, null, " ")) + .mapD((config) => { + if (!this.sendingUpdates) { + console.log("Not sending updates yet! Trigger 'startSendingUpdates' first") + return undefined + } + return JSON.stringify(config, null, " ") + }) .stabilized(100) .addCallbackD(async (config) => { const id = layerId.data @@ -60,10 +68,17 @@ export abstract class EditJsonState { console.warn("No id found in layer, not updating") return } - await server.update(id, config, category) + await this.server.update(id, config, this.category) }) } + public startSavingUpdates(enabled = true) { + this.sendingUpdates = enabled + if (enabled) { + this.configuration.ping() + } + } + public getCurrentValueFor(path: ReadonlyArray): any | undefined { // Walk the path down to see if we find something let entry = this.configuration.data @@ -96,7 +111,7 @@ export abstract class EditJsonState { public register( path: ReadonlyArray, value: Store, - noInitialSync: boolean = false + noInitialSync: boolean = true ): () => void { const unsync = value.addCallback((v) => { this.setValueAt(path, v) @@ -260,6 +275,18 @@ export default class EditLayerState extends EditJsonState { } this.addMissingTagRenderingIds() + + this.configuration.addCallbackAndRunD((layer) => { + if (layer.tagRenderings) { + // A bit of cleanup + const lBefore = layer.tagRenderings.length + const cleaned = Utils.NoNull(layer.tagRenderings) + if (cleaned.length != lBefore) { + layer.tagRenderings = cleaned + this.configuration.ping() + } + } + }) } protected buildValidation(state: DesugaringContext) { @@ -300,6 +327,10 @@ export default class EditLayerState extends EditJsonState { } export class EditThemeState extends EditJsonState { + constructor(schema: ConfigMeta[], server: StudioServer) { + super(schema, server, "themes") + } + protected buildValidation(state: DesugaringContext): Conversion { return new Pipe( new PrepareTheme(state), @@ -307,10 +338,6 @@ export class EditThemeState extends EditJsonState { ) } - constructor(schema: ConfigMeta[], server: StudioServer) { - super(schema, server, "themes") - } - protected getId(): Store { return this.configuration.mapD((config) => config.id) } diff --git a/src/UI/Studio/EditTheme.svelte b/src/UI/Studio/EditTheme.svelte index cd413599c..611c161e5 100644 --- a/src/UI/Studio/EditTheme.svelte +++ b/src/UI/Studio/EditTheme.svelte @@ -10,8 +10,8 @@ export let state: EditThemeState; let schema: ConfigMeta[] = state.schema.filter(schema => schema.path.length > 0); let config = state.configuration; - const messages = state.messages; - const hasErrors = messages.map((m: ConversionMessage[]) => m.filter(m => m.level === "error").length); + let messages = state.messages; + let hasErrors = messages.map((m: ConversionMessage[]) => m.filter(m => m.level === "error").length); let title = state.getStoreFor(["id"]); const wl = window.location; const baseUrl = wl.protocol + "//" + wl.host + "/theme.html?userlayout="; diff --git a/src/UI/Studio/QuestionPreview.svelte b/src/UI/Studio/QuestionPreview.svelte index 71d405604..031b29466 100644 --- a/src/UI/Studio/QuestionPreview.svelte +++ b/src/UI/Studio/QuestionPreview.svelte @@ -5,12 +5,13 @@ import { ImmutableStore, Store } from "../../Logic/UIEventSource"; import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte"; import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; - import * as nmd from "nano-markdown"; + import nmd from "nano-markdown"; import type { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.js"; import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson"; import FromHtml from "../Base/FromHtml.svelte"; + import { Utils } from "../../Utils"; export let state: EditLayerState; export let path: ReadonlyArray; @@ -34,9 +35,15 @@ return [x]; } }); - let configs: Store = configJson.mapD(configs => configs.map(config => new TagRenderingConfig(config))); + let configs: Store =configJson.mapD(configs => Utils.NoNull( configs.map(config => { + try{ + return new TagRenderingConfig(config); + }catch (e) { + return undefined + } + }))); let id: Store = value.mapD(c => { - if (c.id) { + if (c?.id) { return c.id; } if (typeof c === "string") { @@ -49,6 +56,14 @@ let messages = state.messagesFor(path); + let description = schema.description + if(description){ + try{ + description = nmd(description) + }catch (e) { + console.error("Could not convert description to markdown", {description}) + } + }
@@ -63,8 +78,8 @@ {schema.hints.question} {/if} - {#if schema.description} - + {#if description} + {/if} {#each $messages as message}
diff --git a/src/UI/Studio/SchemaBasedField.svelte b/src/UI/Studio/SchemaBasedField.svelte index ad3c247a9..a5e1bc0d6 100644 --- a/src/UI/Studio/SchemaBasedField.svelte +++ b/src/UI/Studio/SchemaBasedField.svelte @@ -16,6 +16,7 @@ export let state: EditLayerState; export let path: (string | number)[] = []; export let schema: ConfigMeta; + export let startInEditModeIfUnset: boolean = false let value = new UIEventSource(undefined); const isTranslation = schema.hints.typehint === "translation" || schema.hints.typehint === "rendered" || ConfigMetaUtils.isTranslation(schema); @@ -118,6 +119,7 @@ } let startValue = state.getCurrentValueFor(path); const tags = new UIEventSource>({ value: startValue }); + let startInEditMode = !startValue && startInEditModeIfUnset try { onDestroy(state.register(path, tags.map(tgs => { const v = tgs["value"]; @@ -157,7 +159,7 @@ {err} {:else}
- + {#if $messages.length > 0} {#each $messages as msg}
{msg.message}
diff --git a/src/UI/Studio/SchemaBasedMultiType.svelte b/src/UI/Studio/SchemaBasedMultiType.svelte index da8d1d67c..2d957e5c9 100644 --- a/src/UI/Studio/SchemaBasedMultiType.svelte +++ b/src/UI/Studio/SchemaBasedMultiType.svelte @@ -149,7 +149,7 @@ } return tags["value"] === "true"; }); - onDestroy(state.register(path, directValue, true)); + onDestroy(state.register(path, directValue)); } let subSchemas: ConfigMeta[] = []; diff --git a/src/UI/Studio/TagRenderingInput.svelte b/src/UI/Studio/TagRenderingInput.svelte index 70aeeabe6..800ce0c79 100644 --- a/src/UI/Studio/TagRenderingInput.svelte +++ b/src/UI/Studio/TagRenderingInput.svelte @@ -72,6 +72,7 @@ const configBuiltin = new TagRenderingConfig( { store.setData(tgs["value"]); @@ -112,7 +113,7 @@ const missing: string[] = questionableTagRenderingSchemaRaw.filter(schema => sch
{#if $allowQuestions} - + {/if} {#each ($mappings ?? []) as mapping, i (mapping)} diff --git a/src/UI/StudioGUI.svelte b/src/UI/StudioGUI.svelte index 92625293c..74146e35c 100644 --- a/src/UI/StudioGUI.svelte +++ b/src/UI/StudioGUI.svelte @@ -24,8 +24,9 @@ import { QuestionMarkCircleIcon } from "@babeard/svelte-heroicons/mini"; import type { ConfigMeta } from "./Studio/configMeta"; import EditTheme from "./Studio/EditTheme.svelte"; - - export let studioUrl = window.location.hostname === "127.0.0.1" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org"; + import * as meta from "../../package.json" + + export let studioUrl = window.location.hostname === "127.0.0.2" ? "http://127.0.0.1:1235" : "https://studio.mapcomplete.org"; let osmConnection = new OsmConnection(new OsmConnection({ oauth_token: QueryParameters.GetQueryParameter( @@ -61,18 +62,22 @@ let layerId = editLayerState.configuration.map(layerConfig => layerConfig.id); let showIntro = UIEventSource.asBoolean(LocalStorageSource.Get("studio-show-intro", "true")); - +const version = meta.version async function editLayer(event: Event) { const layerId: {owner: number, id: string} = event.detail; state = "loading"; + editLayerState.startSavingUpdates(false) editLayerState.configuration.setData(await studio.fetch(layerId.id, "layers", layerId.owner)); + editLayerState.startSavingUpdates() state = "editing_layer"; } async function editTheme(event: Event) { const id : {id: string, owner: number} = event.detail; state = "loading"; + editThemeState.startSavingUpdates(false) editThemeState.configuration.setData(await studio.fetch(id.id, "themes", id.owner)); + editThemeState.startSavingUpdates() state = "editing_theme"; } @@ -153,6 +158,7 @@ Show the introduction again
+ MapComplete version {version}
{:else if state === "edit_layer"} @@ -160,14 +166,14 @@ {state =undefined}}>MapComplete Studio

Choose a layer to edit

- +

Your layers

Layers by other contributors

- +

Official layers by MapComplete

- + {:else if state === "edit_theme"} @@ -175,13 +181,13 @@ {state =undefined}}>MapComplete Studio

Choose a theme to edit

- +

Your themes

Themes by other contributors

- +

Official themes by MapComplete

- + {:else if state === "loading"} diff --git a/src/UI/i18n/Translation.ts b/src/UI/i18n/Translation.ts index 8c1abe804..e7511abb9 100644 --- a/src/UI/i18n/Translation.ts +++ b/src/UI/i18n/Translation.ts @@ -59,7 +59,6 @@ export class Translation extends BaseUIElement { "Constructing a translation, but the object containing translations is empty " + (context ?? "No context given") ) - throw `Constructing a translation, but the object containing translations is empty (${context})` } } diff --git a/src/assets/schemas/layerconfigmeta.json b/src/assets/schemas/layerconfigmeta.json index 897eb3f0c..48ec1f6ff 100644 --- a/src/assets/schemas/layerconfigmeta.json +++ b/src/assets/schemas/layerconfigmeta.json @@ -9816,556 +9816,7 @@ "required": false, "hints": { "question": "Should the created point be snapped to a line layer?", - "suggestions": [ - { - "if": "value=address", - "then": "address - Addresses" - }, - { - "if": "value=advertising", - "then": "advertising - We will complete data from advertising features with reference, operator and lit" - }, - { - "if": "value=ambulancestation", - "then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies." - }, - { - "if": "value=animal_shelter", - "then": "animal_shelter - An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. " - }, - { - "if": "value=artwork", - "then": "artwork - An open map of statues, busts, graffitis and other artwork all over the world" - }, - { - "if": "value=atm", - "then": "atm - ATMs to withdraw money" - }, - { - "if": "value=bank", - "then": "bank - A financial institution to deposit money" - }, - { - "if": "value=barrier", - "then": "barrier - Obstacles while cycling, such as bollards and cycle barriers" - }, - { - "if": "value=bench", - "then": "bench - A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them." - }, - { - "if": "value=bench_at_pt", - "then": "bench_at_pt - A layer showing all public-transport-stops which do have a bench" - }, - { - "if": "value=bicycle_library", - "then": "bicycle_library - A facility where bicycles can be lent for longer period of times" - }, - { - "if": "value=bicycle_rental", - "then": "bicycle_rental - Bicycle rental stations" - }, - { - "if": "value=bicycle_tube_vending_machine", - "then": "bicycle_tube_vending_machine - A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)" - }, - { - "if": "value=bike_cafe", - "then": "bike_cafe - A bike café is a café geared towards cyclists, for example with services such as a pump, with lots of bicycle-related decoration, …" - }, - { - "if": "value=bike_cleaning", - "then": "bike_cleaning - A layer showing facilities where one can clean their bike" - }, - { - "if": "value=bike_parking", - "then": "bike_parking - A layer showing where you can park your bike" - }, - { - "if": "value=bike_repair_station", - "then": "bike_repair_station - A layer showing bicycle pumps and bicycle repair tool stands" - }, - { - "if": "value=bike_shop", - "then": "bike_shop - A shop specifically selling bicycles or related items" - }, - { - "if": "value=bike_themed_object", - "then": "bike_themed_object - A layer with bike-themed objects but who don't match any other layer" - }, - { - "if": "value=binocular", - "then": "binocular - Binoculars" - }, - { - "if": "value=birdhide", - "then": "birdhide - A birdhide" - }, - { - "if": "value=cafe_pub", - "then": "cafe_pub - A layer showing cafés and pubs where one can gather around a drink. The layer asks for some relevant questions" - }, - { - "if": "value=car_rental", - "then": "car_rental - Places where you can rent a car" - }, - { - "if": "value=charging_station", - "then": "charging_station - A charging station" - }, - { - "if": "value=climbing", - "then": "climbing - A dummy layer which contains tagrenderings, shared among the climbing layers" - }, - { - "if": "value=climbing_area", - "then": "climbing_area - An area where climbing is possible, e.g. a crag, site, boulder, … Contains aggregation of routes" - }, - { - "if": "value=climbing_club", - "then": "climbing_club - A climbing club or organisation" - }, - { - "if": "value=climbing_gym", - "then": "climbing_gym - A climbing gym" - }, - { - "if": "value=climbing_opportunity", - "then": "climbing_opportunity - Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added" - }, - { - "if": "value=climbing_route", - "then": "climbing_route - A single climbing route and its properties. Some properties are derived from the containing features" - }, - { - "if": "value=clock", - "then": "clock - Layer with public clocks" - }, - { - "if": "value=conflation", - "then": "conflation - If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme." - }, - { - "if": "value=crab_address", - "then": "crab_address - Address data for Flanders by the governement, suited for import into OpenStreetMap. Datadump from 2021-10-26. This layer contains only visualisation logic. Import buttons should be added via an override. Note that HNRLABEL contains the original value, whereas _HNRLABEL contains a slightly cleaned version" - }, - { - "if": "value=crossings", - "then": "crossings - Crossings for pedestrians and cyclists" - }, - { - "if": "value=current_view", - "then": "current_view - A meta-layer which contains one single feature, namely the bounding box of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'." - }, - { - "if": "value=cycleways_and_roads", - "then": "cycleways_and_roads - All infrastructure that someone can cycle over, accompanied with questions about this infrastructure" - }, - { - "if": "value=defibrillator", - "then": "defibrillator - A layer showing defibrillators which can be used in case of emergency. This contains public defibrillators, but also defibrillators which might need staff to fetch the actual device" - }, - { - "if": "value=dentist", - "then": "dentist - This layer shows dentist offices" - }, - { - "if": "value=direction", - "then": "direction - This layer visualizes directions" - }, - { - "if": "value=doctors", - "then": "doctors - This layer shows doctor offices" - }, - { - "if": "value=dogpark", - "then": "dogpark - A layer showing dogparks, which are areas where dog are allowed to run without a leash" - }, - { - "if": "value=drinking_water", - "then": "drinking_water - A layer showing drinking water fountains" - }, - { - "if": "value=elevator", - "then": "elevator - This layer show elevators and asks for operational status and elevator dimensions. Useful for wheelchair accessibility information" - }, - { - "if": "value=elongated_coin", - "then": "elongated_coin - Layer showing penny presses." - }, - { - "if": "value=entrance", - "then": "entrance - A layer showing entrances and offering capabilities to survey some advanced data which is important for e.g. wheelchair users (but also bicycle users, people who want to deliver, …)" - }, - { - "if": "value=etymology", - "then": "etymology - All objects which have an etymology known" - }, - { - "if": "value=extinguisher", - "then": "extinguisher - Map layer to show fire extinguishers." - }, - { - "if": "value=filters", - "then": "filters - This layer acts as library for common filters" - }, - { - "if": "value=fire_station", - "then": "fire_station - Map layer to show fire stations." - }, - { - "if": "value=fitness_centre", - "then": "fitness_centre - Layer showing fitness centres" - }, - { - "if": "value=fitness_station", - "then": "fitness_station - Find a fitness station near you, and add missing ones." - }, - { - "if": "value=fixme", - "then": "fixme - OSM objects that likely need to be fixed, based on a FIXME tag." - }, - { - "if": "value=food", - "then": "food - A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" - }, - { - "if": "value=ghost_bike", - "then": "ghost_bike - A layer showing memorials for cyclists, killed in road accidents" - }, - { - "if": "value=governments", - "then": "governments - This layer show governmental buildings. It was setup as commissioned layer for the client of OSOC '22" - }, - { - "if": "value=gps_location", - "then": "gps_location - Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, ...." - }, - { - "if": "value=gps_location_history", - "then": "gps_location_history - Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object" - }, - { - "if": "value=gps_track", - "then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track." - }, - { - "if": "value=guidepost", - "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations" - }, - { - "if": "value=hackerspace", - "then": "hackerspace - Hackerspace" - }, - { - "if": "value=home_location", - "then": "home_location - Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap." - }, - { - "if": "value=hospital", - "then": "hospital - A layer showing hospital grounds" - }, - { - "if": "value=hotel", - "then": "hotel - Layer showing all hotels" - }, - { - "if": "value=hydrant", - "then": "hydrant - Map layer to show fire hydrants." - }, - { - "if": "value=ice_cream", - "then": "ice_cream - A place where ice cream is sold over the counter" - }, - { - "if": "value=icons", - "then": "icons - A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" - }, - { - "if": "value=id_presets", - "then": "id_presets - Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." - }, - { - "if": "value=import_candidate", - "then": "import_candidate - Layer used as template in the importHelper" - }, - { - "if": "value=indoors", - "then": "indoors - Basic indoor mapping: shows room outlines" - }, - { - "if": "value=information_board", - "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" - }, - { - "if": "value=kerbs", - "then": "kerbs - A layer showing kerbs." - }, - { - "if": "value=kindergarten_childcare", - "then": "kindergarten_childcare - Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other" - }, - { - "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" - }, - { - "if": "value=map", - "then": "map - A map, meant for tourists which is permanently installed in the public space" - }, - { - "if": "value=maproulette", - "then": "maproulette - Layer showing all tasks in MapRoulette" - }, - { - "if": "value=maproulette_challenge", - "then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this." - }, - { - "if": "value=maxspeed", - "then": "maxspeed - Shows the allowed speed for every road" - }, - { - "if": "value=memorial", - "then": "memorial - Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on" - }, - { - "if": "value=named_streets", - "then": "named_streets - Hidden layer with all streets which have a name. Useful to detect addresses" - }, - { - "if": "value=nature_reserve", - "then": "nature_reserve - A nature reserve is an area where nature can take its course" - }, - { - "if": "value=note", - "then": "note - This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes)" - }, - { - "if": "value=observation_tower", - "then": "observation_tower - Towers with a panoramic view" - }, - { - "if": "value=osm_community_index", - "then": "osm_community_index - A layer showing the OpenStreetMap Communities" - }, - { - "if": "value=parcel_lockers", - "then": "parcel_lockers - Layer showing parcel lockers for collecting and sending parcels." - }, - { - "if": "value=parking", - "then": "parking - A layer showing car parkings" - }, - { - "if": "value=parking_spaces", - "then": "parking_spaces - Layer showing individual parking spaces." - }, - { - "if": "value=parking_ticket_machine", - "then": "parking_ticket_machine - Layer with parking ticket machines to pay for parking." - }, - { - "if": "value=pedestrian_path", - "then": "pedestrian_path - Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer" - }, - { - "if": "value=pharmacy", - "then": "pharmacy - A layer showing pharmacies, which (probably) dispense prescription drugs" - }, - { - "if": "value=physiotherapist", - "then": "physiotherapist - This layer shows physiotherapists" - }, - { - "if": "value=picnic_table", - "then": "picnic_table - The layer showing picnic tables" - }, - { - "if": "value=play_forest", - "then": "play_forest - Een speelbos is een vrij toegankelijke zone in een bos" - }, - { - "if": "value=playground", - "then": "playground - Playgrounds" - }, - { - "if": "value=postboxes", - "then": "postboxes - The layer showing postboxes." - }, - { - "if": "value=postoffices", - "then": "postoffices - A layer showing post offices." - }, - { - "if": "value=public_bookcase", - "then": "public_bookcase - A streetside cabinet with books, accessible to anyone" - }, - { - "if": "value=questions", - "then": "questions - Special library layer which does not need a '.questions'-prefix before being imported" - }, - { - "if": "value=railway_platforms", - "then": "railway_platforms - Find every platform in the station, and the train routes that use them." - }, - { - "if": "value=rainbow_crossings", - "then": "rainbow_crossings - A layer showing pedestrian crossings with rainbow paintings" - }, - { - "if": "value=range", - "then": "range - Meta-layer, simply showing a bbox in red" - }, - { - "if": "value=reception_desk", - "then": "reception_desk - A layer showing where the reception desks are and which asks some accessibility information" - }, - { - "if": "value=recycling", - "then": "recycling - A layer with recycling containers and centres" - }, - { - "if": "value=route_marker", - "then": "route_marker - Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." - }, - { - "if": "value=school", - "then": "school - Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles" - }, - { - "if": "value=selected_element", - "then": "selected_element - Highlights the currently selected element. Override this layer to have different colors" - }, - { - "if": "value=shelter", - "then": "shelter - Layer showing shelter structures" - }, - { - "if": "value=shops", - "then": "shops - A shop" - }, - { - "if": "value=shower", - "then": "shower - A layer showing (public) showers" - }, - { - "if": "value=slow_roads", - "then": "slow_roads - All carfree roads" - }, - { - "if": "value=speed_camera", - "then": "speed_camera - Layer showing speed cameras" - }, - { - "if": "value=speed_display", - "then": "speed_display - Layer showing speed displays that alert drivers of their speed." - }, - { - "if": "value=split_point", - "then": "split_point - Layer rendering the little scissors for the minimap in the 'splitRoadWizard'" - }, - { - "if": "value=split_road", - "then": "split_road - Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible" - }, - { - "if": "value=sport_pitch", - "then": "sport_pitch - A sport pitch" - }, - { - "if": "value=sports_centre", - "then": "sports_centre - Indoor and outdoor sports centres can be found on this layer" - }, - { - "if": "value=stairs", - "then": "stairs - Layer showing stairs and escalators" - }, - { - "if": "value=street_lamps", - "then": "street_lamps - A layer showing street lights" - }, - { - "if": "value=surveillance_camera", - "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" - }, - { - "if": "value=tertiary_education", - "then": "tertiary_education - Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)" - }, - { - "if": "value=ticket_machine", - "then": "ticket_machine - Find ticket machines for public transport tickets" - }, - { - "if": "value=ticket_validator", - "then": "ticket_validator - Find ticket validators to validate public transport tickets" - }, - { - "if": "value=toilet", - "then": "toilet - A layer showing (public) toilets" - }, - { - "if": "value=toilet_at_amenity", - "then": "toilet_at_amenity - A layer showing (public) toilets located at different places." - }, - { - "if": "value=trail", - "then": "trail - Aangeduide wandeltochten" - }, - { - "if": "value=transit_routes", - "then": "transit_routes - Layer showing bus lines" - }, - { - "if": "value=transit_stops", - "then": "transit_stops - Layer showing different types of transit stops." - }, - { - "if": "value=tree_node", - "then": "tree_node - A layer showing trees" - }, - { - "if": "value=usersettings", - "then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings" - }, - { - "if": "value=vending_machine", - "then": "vending_machine - Layer showing vending machines" - }, - { - "if": "value=veterinary", - "then": "veterinary - A layer showing veterinarians" - }, - { - "if": "value=viewpoint", - "then": "viewpoint - A nice viewpoint or nice view. Ideal to add an image if no other category fits" - }, - { - "if": "value=village_green", - "then": "village_green - A layer showing village-green (which are communal green areas, but not quite parks)" - }, - { - "if": "value=visitor_information_centre", - "then": "visitor_information_centre - A visitor center offers information about a specific attraction or place of interest where it is located." - }, - { - "if": "value=walls_and_buildings", - "then": "walls_and_buildings - Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user." - }, - { - "if": "value=waste_basket", - "then": "waste_basket - This is a public waste basket, thrash can, where you can throw away your thrash." - }, - { - "if": "value=waste_disposal", - "then": "waste_disposal - Waste Disposal Bin, medium to large bin for disposal of (household) waste" - }, - { - "if": "value=windturbine", - "then": "windturbine - Modern windmills generating electricity" - } - ] + "suggestions": [] }, "type": "array", "description": "If specified, these layers will be shown in the precise location picker and the new point will be snapped towards it.\nFor example, this can be used to snap against `walls_and_buildings` (e.g. to attach a defibrillator, an entrance, an artwork, ... to the wall)\nor to snap an obstacle (such as a bollard) to the `cycleways_and_roads`." @@ -10418,7 +9869,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -10426,7 +9877,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -11150,7 +10609,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -11255,6 +10714,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -12277,7 +11744,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -12384,6 +11851,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -13437,7 +12912,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -13544,6 +13019,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -14609,7 +14092,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -14718,6 +14201,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { diff --git a/src/assets/schemas/layoutconfigmeta.json b/src/assets/schemas/layoutconfigmeta.json index d92e2cd93..6f0734163 100644 --- a/src/assets/schemas/layoutconfigmeta.json +++ b/src/assets/schemas/layoutconfigmeta.json @@ -323,556 +323,7 @@ "types": "hidden | layer | hidden", "group": "layers", "question": "What layers should this map show?", - "suggestions": [ - { - "if": "value=address", - "then": "address - Addresses" - }, - { - "if": "value=advertising", - "then": "advertising - We will complete data from advertising features with reference, operator and lit" - }, - { - "if": "value=ambulancestation", - "then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies." - }, - { - "if": "value=animal_shelter", - "then": "animal_shelter - An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. " - }, - { - "if": "value=artwork", - "then": "artwork - An open map of statues, busts, graffitis and other artwork all over the world" - }, - { - "if": "value=atm", - "then": "atm - ATMs to withdraw money" - }, - { - "if": "value=bank", - "then": "bank - A financial institution to deposit money" - }, - { - "if": "value=barrier", - "then": "barrier - Obstacles while cycling, such as bollards and cycle barriers" - }, - { - "if": "value=bench", - "then": "bench - A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them." - }, - { - "if": "value=bench_at_pt", - "then": "bench_at_pt - A layer showing all public-transport-stops which do have a bench" - }, - { - "if": "value=bicycle_library", - "then": "bicycle_library - A facility where bicycles can be lent for longer period of times" - }, - { - "if": "value=bicycle_rental", - "then": "bicycle_rental - Bicycle rental stations" - }, - { - "if": "value=bicycle_tube_vending_machine", - "then": "bicycle_tube_vending_machine - A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)" - }, - { - "if": "value=bike_cafe", - "then": "bike_cafe - A bike café is a café geared towards cyclists, for example with services such as a pump, with lots of bicycle-related decoration, …" - }, - { - "if": "value=bike_cleaning", - "then": "bike_cleaning - A layer showing facilities where one can clean their bike" - }, - { - "if": "value=bike_parking", - "then": "bike_parking - A layer showing where you can park your bike" - }, - { - "if": "value=bike_repair_station", - "then": "bike_repair_station - A layer showing bicycle pumps and bicycle repair tool stands" - }, - { - "if": "value=bike_shop", - "then": "bike_shop - A shop specifically selling bicycles or related items" - }, - { - "if": "value=bike_themed_object", - "then": "bike_themed_object - A layer with bike-themed objects but who don't match any other layer" - }, - { - "if": "value=binocular", - "then": "binocular - Binoculars" - }, - { - "if": "value=birdhide", - "then": "birdhide - A birdhide" - }, - { - "if": "value=cafe_pub", - "then": "cafe_pub - A layer showing cafés and pubs where one can gather around a drink. The layer asks for some relevant questions" - }, - { - "if": "value=car_rental", - "then": "car_rental - Places where you can rent a car" - }, - { - "if": "value=charging_station", - "then": "charging_station - A charging station" - }, - { - "if": "value=climbing", - "then": "climbing - A dummy layer which contains tagrenderings, shared among the climbing layers" - }, - { - "if": "value=climbing_area", - "then": "climbing_area - An area where climbing is possible, e.g. a crag, site, boulder, … Contains aggregation of routes" - }, - { - "if": "value=climbing_club", - "then": "climbing_club - A climbing club or organisation" - }, - { - "if": "value=climbing_gym", - "then": "climbing_gym - A climbing gym" - }, - { - "if": "value=climbing_opportunity", - "then": "climbing_opportunity - Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added" - }, - { - "if": "value=climbing_route", - "then": "climbing_route - A single climbing route and its properties. Some properties are derived from the containing features" - }, - { - "if": "value=clock", - "then": "clock - Layer with public clocks" - }, - { - "if": "value=conflation", - "then": "conflation - If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme." - }, - { - "if": "value=crab_address", - "then": "crab_address - Address data for Flanders by the governement, suited for import into OpenStreetMap. Datadump from 2021-10-26. This layer contains only visualisation logic. Import buttons should be added via an override. Note that HNRLABEL contains the original value, whereas _HNRLABEL contains a slightly cleaned version" - }, - { - "if": "value=crossings", - "then": "crossings - Crossings for pedestrians and cyclists" - }, - { - "if": "value=current_view", - "then": "current_view - A meta-layer which contains one single feature, namely the bounding box of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'." - }, - { - "if": "value=cycleways_and_roads", - "then": "cycleways_and_roads - All infrastructure that someone can cycle over, accompanied with questions about this infrastructure" - }, - { - "if": "value=defibrillator", - "then": "defibrillator - A layer showing defibrillators which can be used in case of emergency. This contains public defibrillators, but also defibrillators which might need staff to fetch the actual device" - }, - { - "if": "value=dentist", - "then": "dentist - This layer shows dentist offices" - }, - { - "if": "value=direction", - "then": "direction - This layer visualizes directions" - }, - { - "if": "value=doctors", - "then": "doctors - This layer shows doctor offices" - }, - { - "if": "value=dogpark", - "then": "dogpark - A layer showing dogparks, which are areas where dog are allowed to run without a leash" - }, - { - "if": "value=drinking_water", - "then": "drinking_water - A layer showing drinking water fountains" - }, - { - "if": "value=elevator", - "then": "elevator - This layer show elevators and asks for operational status and elevator dimensions. Useful for wheelchair accessibility information" - }, - { - "if": "value=elongated_coin", - "then": "elongated_coin - Layer showing penny presses." - }, - { - "if": "value=entrance", - "then": "entrance - A layer showing entrances and offering capabilities to survey some advanced data which is important for e.g. wheelchair users (but also bicycle users, people who want to deliver, …)" - }, - { - "if": "value=etymology", - "then": "etymology - All objects which have an etymology known" - }, - { - "if": "value=extinguisher", - "then": "extinguisher - Map layer to show fire extinguishers." - }, - { - "if": "value=filters", - "then": "filters - This layer acts as library for common filters" - }, - { - "if": "value=fire_station", - "then": "fire_station - Map layer to show fire stations." - }, - { - "if": "value=fitness_centre", - "then": "fitness_centre - Layer showing fitness centres" - }, - { - "if": "value=fitness_station", - "then": "fitness_station - Find a fitness station near you, and add missing ones." - }, - { - "if": "value=fixme", - "then": "fixme - OSM objects that likely need to be fixed, based on a FIXME tag." - }, - { - "if": "value=food", - "then": "food - A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" - }, - { - "if": "value=ghost_bike", - "then": "ghost_bike - A layer showing memorials for cyclists, killed in road accidents" - }, - { - "if": "value=governments", - "then": "governments - This layer show governmental buildings. It was setup as commissioned layer for the client of OSOC '22" - }, - { - "if": "value=gps_location", - "then": "gps_location - Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, ...." - }, - { - "if": "value=gps_location_history", - "then": "gps_location_history - Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object" - }, - { - "if": "value=gps_track", - "then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track." - }, - { - "if": "value=guidepost", - "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations" - }, - { - "if": "value=hackerspace", - "then": "hackerspace - Hackerspace" - }, - { - "if": "value=home_location", - "then": "home_location - Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap." - }, - { - "if": "value=hospital", - "then": "hospital - A layer showing hospital grounds" - }, - { - "if": "value=hotel", - "then": "hotel - Layer showing all hotels" - }, - { - "if": "value=hydrant", - "then": "hydrant - Map layer to show fire hydrants." - }, - { - "if": "value=ice_cream", - "then": "ice_cream - A place where ice cream is sold over the counter" - }, - { - "if": "value=icons", - "then": "icons - A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" - }, - { - "if": "value=id_presets", - "then": "id_presets - Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." - }, - { - "if": "value=import_candidate", - "then": "import_candidate - Layer used as template in the importHelper" - }, - { - "if": "value=indoors", - "then": "indoors - Basic indoor mapping: shows room outlines" - }, - { - "if": "value=information_board", - "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" - }, - { - "if": "value=kerbs", - "then": "kerbs - A layer showing kerbs." - }, - { - "if": "value=kindergarten_childcare", - "then": "kindergarten_childcare - Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other" - }, - { - "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" - }, - { - "if": "value=map", - "then": "map - A map, meant for tourists which is permanently installed in the public space" - }, - { - "if": "value=maproulette", - "then": "maproulette - Layer showing all tasks in MapRoulette" - }, - { - "if": "value=maproulette_challenge", - "then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this." - }, - { - "if": "value=maxspeed", - "then": "maxspeed - Shows the allowed speed for every road" - }, - { - "if": "value=memorial", - "then": "memorial - Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on" - }, - { - "if": "value=named_streets", - "then": "named_streets - Hidden layer with all streets which have a name. Useful to detect addresses" - }, - { - "if": "value=nature_reserve", - "then": "nature_reserve - A nature reserve is an area where nature can take its course" - }, - { - "if": "value=note", - "then": "note - This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes)" - }, - { - "if": "value=observation_tower", - "then": "observation_tower - Towers with a panoramic view" - }, - { - "if": "value=osm_community_index", - "then": "osm_community_index - A layer showing the OpenStreetMap Communities" - }, - { - "if": "value=parcel_lockers", - "then": "parcel_lockers - Layer showing parcel lockers for collecting and sending parcels." - }, - { - "if": "value=parking", - "then": "parking - A layer showing car parkings" - }, - { - "if": "value=parking_spaces", - "then": "parking_spaces - Layer showing individual parking spaces." - }, - { - "if": "value=parking_ticket_machine", - "then": "parking_ticket_machine - Layer with parking ticket machines to pay for parking." - }, - { - "if": "value=pedestrian_path", - "then": "pedestrian_path - Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer" - }, - { - "if": "value=pharmacy", - "then": "pharmacy - A layer showing pharmacies, which (probably) dispense prescription drugs" - }, - { - "if": "value=physiotherapist", - "then": "physiotherapist - This layer shows physiotherapists" - }, - { - "if": "value=picnic_table", - "then": "picnic_table - The layer showing picnic tables" - }, - { - "if": "value=play_forest", - "then": "play_forest - Een speelbos is een vrij toegankelijke zone in een bos" - }, - { - "if": "value=playground", - "then": "playground - Playgrounds" - }, - { - "if": "value=postboxes", - "then": "postboxes - The layer showing postboxes." - }, - { - "if": "value=postoffices", - "then": "postoffices - A layer showing post offices." - }, - { - "if": "value=public_bookcase", - "then": "public_bookcase - A streetside cabinet with books, accessible to anyone" - }, - { - "if": "value=questions", - "then": "questions - Special library layer which does not need a '.questions'-prefix before being imported" - }, - { - "if": "value=railway_platforms", - "then": "railway_platforms - Find every platform in the station, and the train routes that use them." - }, - { - "if": "value=rainbow_crossings", - "then": "rainbow_crossings - A layer showing pedestrian crossings with rainbow paintings" - }, - { - "if": "value=range", - "then": "range - Meta-layer, simply showing a bbox in red" - }, - { - "if": "value=reception_desk", - "then": "reception_desk - A layer showing where the reception desks are and which asks some accessibility information" - }, - { - "if": "value=recycling", - "then": "recycling - A layer with recycling containers and centres" - }, - { - "if": "value=route_marker", - "then": "route_marker - Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." - }, - { - "if": "value=school", - "then": "school - Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles" - }, - { - "if": "value=selected_element", - "then": "selected_element - Highlights the currently selected element. Override this layer to have different colors" - }, - { - "if": "value=shelter", - "then": "shelter - Layer showing shelter structures" - }, - { - "if": "value=shops", - "then": "shops - A shop" - }, - { - "if": "value=shower", - "then": "shower - A layer showing (public) showers" - }, - { - "if": "value=slow_roads", - "then": "slow_roads - All carfree roads" - }, - { - "if": "value=speed_camera", - "then": "speed_camera - Layer showing speed cameras" - }, - { - "if": "value=speed_display", - "then": "speed_display - Layer showing speed displays that alert drivers of their speed." - }, - { - "if": "value=split_point", - "then": "split_point - Layer rendering the little scissors for the minimap in the 'splitRoadWizard'" - }, - { - "if": "value=split_road", - "then": "split_road - Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible" - }, - { - "if": "value=sport_pitch", - "then": "sport_pitch - A sport pitch" - }, - { - "if": "value=sports_centre", - "then": "sports_centre - Indoor and outdoor sports centres can be found on this layer" - }, - { - "if": "value=stairs", - "then": "stairs - Layer showing stairs and escalators" - }, - { - "if": "value=street_lamps", - "then": "street_lamps - A layer showing street lights" - }, - { - "if": "value=surveillance_camera", - "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" - }, - { - "if": "value=tertiary_education", - "then": "tertiary_education - Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)" - }, - { - "if": "value=ticket_machine", - "then": "ticket_machine - Find ticket machines for public transport tickets" - }, - { - "if": "value=ticket_validator", - "then": "ticket_validator - Find ticket validators to validate public transport tickets" - }, - { - "if": "value=toilet", - "then": "toilet - A layer showing (public) toilets" - }, - { - "if": "value=toilet_at_amenity", - "then": "toilet_at_amenity - A layer showing (public) toilets located at different places." - }, - { - "if": "value=trail", - "then": "trail - Aangeduide wandeltochten" - }, - { - "if": "value=transit_routes", - "then": "transit_routes - Layer showing bus lines" - }, - { - "if": "value=transit_stops", - "then": "transit_stops - Layer showing different types of transit stops." - }, - { - "if": "value=tree_node", - "then": "tree_node - A layer showing trees" - }, - { - "if": "value=usersettings", - "then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings" - }, - { - "if": "value=vending_machine", - "then": "vending_machine - Layer showing vending machines" - }, - { - "if": "value=veterinary", - "then": "veterinary - A layer showing veterinarians" - }, - { - "if": "value=viewpoint", - "then": "viewpoint - A nice viewpoint or nice view. Ideal to add an image if no other category fits" - }, - { - "if": "value=village_green", - "then": "village_green - A layer showing village-green (which are communal green areas, but not quite parks)" - }, - { - "if": "value=visitor_information_centre", - "then": "visitor_information_centre - A visitor center offers information about a specific attraction or place of interest where it is located." - }, - { - "if": "value=walls_and_buildings", - "then": "walls_and_buildings - Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user." - }, - { - "if": "value=waste_basket", - "then": "waste_basket - This is a public waste basket, thrash can, where you can throw away your thrash." - }, - { - "if": "value=waste_disposal", - "then": "waste_disposal - Waste Disposal Bin, medium to large bin for disposal of (household) waste" - }, - { - "if": "value=windturbine", - "then": "windturbine - Modern windmills generating electricity" - } - ] + "suggestions": [] }, "type": [ { @@ -1330,7 +781,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -1338,7 +789,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -11910,556 +11369,7 @@ "required": false, "hints": { "question": "Should the created point be snapped to a line layer?", - "suggestions": [ - { - "if": "value=address", - "then": "address - Addresses" - }, - { - "if": "value=advertising", - "then": "advertising - We will complete data from advertising features with reference, operator and lit" - }, - { - "if": "value=ambulancestation", - "then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies." - }, - { - "if": "value=animal_shelter", - "then": "animal_shelter - An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. " - }, - { - "if": "value=artwork", - "then": "artwork - An open map of statues, busts, graffitis and other artwork all over the world" - }, - { - "if": "value=atm", - "then": "atm - ATMs to withdraw money" - }, - { - "if": "value=bank", - "then": "bank - A financial institution to deposit money" - }, - { - "if": "value=barrier", - "then": "barrier - Obstacles while cycling, such as bollards and cycle barriers" - }, - { - "if": "value=bench", - "then": "bench - A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them." - }, - { - "if": "value=bench_at_pt", - "then": "bench_at_pt - A layer showing all public-transport-stops which do have a bench" - }, - { - "if": "value=bicycle_library", - "then": "bicycle_library - A facility where bicycles can be lent for longer period of times" - }, - { - "if": "value=bicycle_rental", - "then": "bicycle_rental - Bicycle rental stations" - }, - { - "if": "value=bicycle_tube_vending_machine", - "then": "bicycle_tube_vending_machine - A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)" - }, - { - "if": "value=bike_cafe", - "then": "bike_cafe - A bike café is a café geared towards cyclists, for example with services such as a pump, with lots of bicycle-related decoration, …" - }, - { - "if": "value=bike_cleaning", - "then": "bike_cleaning - A layer showing facilities where one can clean their bike" - }, - { - "if": "value=bike_parking", - "then": "bike_parking - A layer showing where you can park your bike" - }, - { - "if": "value=bike_repair_station", - "then": "bike_repair_station - A layer showing bicycle pumps and bicycle repair tool stands" - }, - { - "if": "value=bike_shop", - "then": "bike_shop - A shop specifically selling bicycles or related items" - }, - { - "if": "value=bike_themed_object", - "then": "bike_themed_object - A layer with bike-themed objects but who don't match any other layer" - }, - { - "if": "value=binocular", - "then": "binocular - Binoculars" - }, - { - "if": "value=birdhide", - "then": "birdhide - A birdhide" - }, - { - "if": "value=cafe_pub", - "then": "cafe_pub - A layer showing cafés and pubs where one can gather around a drink. The layer asks for some relevant questions" - }, - { - "if": "value=car_rental", - "then": "car_rental - Places where you can rent a car" - }, - { - "if": "value=charging_station", - "then": "charging_station - A charging station" - }, - { - "if": "value=climbing", - "then": "climbing - A dummy layer which contains tagrenderings, shared among the climbing layers" - }, - { - "if": "value=climbing_area", - "then": "climbing_area - An area where climbing is possible, e.g. a crag, site, boulder, … Contains aggregation of routes" - }, - { - "if": "value=climbing_club", - "then": "climbing_club - A climbing club or organisation" - }, - { - "if": "value=climbing_gym", - "then": "climbing_gym - A climbing gym" - }, - { - "if": "value=climbing_opportunity", - "then": "climbing_opportunity - Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added" - }, - { - "if": "value=climbing_route", - "then": "climbing_route - A single climbing route and its properties. Some properties are derived from the containing features" - }, - { - "if": "value=clock", - "then": "clock - Layer with public clocks" - }, - { - "if": "value=conflation", - "then": "conflation - If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme." - }, - { - "if": "value=crab_address", - "then": "crab_address - Address data for Flanders by the governement, suited for import into OpenStreetMap. Datadump from 2021-10-26. This layer contains only visualisation logic. Import buttons should be added via an override. Note that HNRLABEL contains the original value, whereas _HNRLABEL contains a slightly cleaned version" - }, - { - "if": "value=crossings", - "then": "crossings - Crossings for pedestrians and cyclists" - }, - { - "if": "value=current_view", - "then": "current_view - A meta-layer which contains one single feature, namely the bounding box of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'." - }, - { - "if": "value=cycleways_and_roads", - "then": "cycleways_and_roads - All infrastructure that someone can cycle over, accompanied with questions about this infrastructure" - }, - { - "if": "value=defibrillator", - "then": "defibrillator - A layer showing defibrillators which can be used in case of emergency. This contains public defibrillators, but also defibrillators which might need staff to fetch the actual device" - }, - { - "if": "value=dentist", - "then": "dentist - This layer shows dentist offices" - }, - { - "if": "value=direction", - "then": "direction - This layer visualizes directions" - }, - { - "if": "value=doctors", - "then": "doctors - This layer shows doctor offices" - }, - { - "if": "value=dogpark", - "then": "dogpark - A layer showing dogparks, which are areas where dog are allowed to run without a leash" - }, - { - "if": "value=drinking_water", - "then": "drinking_water - A layer showing drinking water fountains" - }, - { - "if": "value=elevator", - "then": "elevator - This layer show elevators and asks for operational status and elevator dimensions. Useful for wheelchair accessibility information" - }, - { - "if": "value=elongated_coin", - "then": "elongated_coin - Layer showing penny presses." - }, - { - "if": "value=entrance", - "then": "entrance - A layer showing entrances and offering capabilities to survey some advanced data which is important for e.g. wheelchair users (but also bicycle users, people who want to deliver, …)" - }, - { - "if": "value=etymology", - "then": "etymology - All objects which have an etymology known" - }, - { - "if": "value=extinguisher", - "then": "extinguisher - Map layer to show fire extinguishers." - }, - { - "if": "value=filters", - "then": "filters - This layer acts as library for common filters" - }, - { - "if": "value=fire_station", - "then": "fire_station - Map layer to show fire stations." - }, - { - "if": "value=fitness_centre", - "then": "fitness_centre - Layer showing fitness centres" - }, - { - "if": "value=fitness_station", - "then": "fitness_station - Find a fitness station near you, and add missing ones." - }, - { - "if": "value=fixme", - "then": "fixme - OSM objects that likely need to be fixed, based on a FIXME tag." - }, - { - "if": "value=food", - "then": "food - A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" - }, - { - "if": "value=ghost_bike", - "then": "ghost_bike - A layer showing memorials for cyclists, killed in road accidents" - }, - { - "if": "value=governments", - "then": "governments - This layer show governmental buildings. It was setup as commissioned layer for the client of OSOC '22" - }, - { - "if": "value=gps_location", - "then": "gps_location - Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, ...." - }, - { - "if": "value=gps_location_history", - "then": "gps_location_history - Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object" - }, - { - "if": "value=gps_track", - "then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track." - }, - { - "if": "value=guidepost", - "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations" - }, - { - "if": "value=hackerspace", - "then": "hackerspace - Hackerspace" - }, - { - "if": "value=home_location", - "then": "home_location - Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap." - }, - { - "if": "value=hospital", - "then": "hospital - A layer showing hospital grounds" - }, - { - "if": "value=hotel", - "then": "hotel - Layer showing all hotels" - }, - { - "if": "value=hydrant", - "then": "hydrant - Map layer to show fire hydrants." - }, - { - "if": "value=ice_cream", - "then": "ice_cream - A place where ice cream is sold over the counter" - }, - { - "if": "value=icons", - "then": "icons - A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" - }, - { - "if": "value=id_presets", - "then": "id_presets - Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." - }, - { - "if": "value=import_candidate", - "then": "import_candidate - Layer used as template in the importHelper" - }, - { - "if": "value=indoors", - "then": "indoors - Basic indoor mapping: shows room outlines" - }, - { - "if": "value=information_board", - "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" - }, - { - "if": "value=kerbs", - "then": "kerbs - A layer showing kerbs." - }, - { - "if": "value=kindergarten_childcare", - "then": "kindergarten_childcare - Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other" - }, - { - "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" - }, - { - "if": "value=map", - "then": "map - A map, meant for tourists which is permanently installed in the public space" - }, - { - "if": "value=maproulette", - "then": "maproulette - Layer showing all tasks in MapRoulette" - }, - { - "if": "value=maproulette_challenge", - "then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this." - }, - { - "if": "value=maxspeed", - "then": "maxspeed - Shows the allowed speed for every road" - }, - { - "if": "value=memorial", - "then": "memorial - Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on" - }, - { - "if": "value=named_streets", - "then": "named_streets - Hidden layer with all streets which have a name. Useful to detect addresses" - }, - { - "if": "value=nature_reserve", - "then": "nature_reserve - A nature reserve is an area where nature can take its course" - }, - { - "if": "value=note", - "then": "note - This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes)" - }, - { - "if": "value=observation_tower", - "then": "observation_tower - Towers with a panoramic view" - }, - { - "if": "value=osm_community_index", - "then": "osm_community_index - A layer showing the OpenStreetMap Communities" - }, - { - "if": "value=parcel_lockers", - "then": "parcel_lockers - Layer showing parcel lockers for collecting and sending parcels." - }, - { - "if": "value=parking", - "then": "parking - A layer showing car parkings" - }, - { - "if": "value=parking_spaces", - "then": "parking_spaces - Layer showing individual parking spaces." - }, - { - "if": "value=parking_ticket_machine", - "then": "parking_ticket_machine - Layer with parking ticket machines to pay for parking." - }, - { - "if": "value=pedestrian_path", - "then": "pedestrian_path - Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer" - }, - { - "if": "value=pharmacy", - "then": "pharmacy - A layer showing pharmacies, which (probably) dispense prescription drugs" - }, - { - "if": "value=physiotherapist", - "then": "physiotherapist - This layer shows physiotherapists" - }, - { - "if": "value=picnic_table", - "then": "picnic_table - The layer showing picnic tables" - }, - { - "if": "value=play_forest", - "then": "play_forest - Een speelbos is een vrij toegankelijke zone in een bos" - }, - { - "if": "value=playground", - "then": "playground - Playgrounds" - }, - { - "if": "value=postboxes", - "then": "postboxes - The layer showing postboxes." - }, - { - "if": "value=postoffices", - "then": "postoffices - A layer showing post offices." - }, - { - "if": "value=public_bookcase", - "then": "public_bookcase - A streetside cabinet with books, accessible to anyone" - }, - { - "if": "value=questions", - "then": "questions - Special library layer which does not need a '.questions'-prefix before being imported" - }, - { - "if": "value=railway_platforms", - "then": "railway_platforms - Find every platform in the station, and the train routes that use them." - }, - { - "if": "value=rainbow_crossings", - "then": "rainbow_crossings - A layer showing pedestrian crossings with rainbow paintings" - }, - { - "if": "value=range", - "then": "range - Meta-layer, simply showing a bbox in red" - }, - { - "if": "value=reception_desk", - "then": "reception_desk - A layer showing where the reception desks are and which asks some accessibility information" - }, - { - "if": "value=recycling", - "then": "recycling - A layer with recycling containers and centres" - }, - { - "if": "value=route_marker", - "then": "route_marker - Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." - }, - { - "if": "value=school", - "then": "school - Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles" - }, - { - "if": "value=selected_element", - "then": "selected_element - Highlights the currently selected element. Override this layer to have different colors" - }, - { - "if": "value=shelter", - "then": "shelter - Layer showing shelter structures" - }, - { - "if": "value=shops", - "then": "shops - A shop" - }, - { - "if": "value=shower", - "then": "shower - A layer showing (public) showers" - }, - { - "if": "value=slow_roads", - "then": "slow_roads - All carfree roads" - }, - { - "if": "value=speed_camera", - "then": "speed_camera - Layer showing speed cameras" - }, - { - "if": "value=speed_display", - "then": "speed_display - Layer showing speed displays that alert drivers of their speed." - }, - { - "if": "value=split_point", - "then": "split_point - Layer rendering the little scissors for the minimap in the 'splitRoadWizard'" - }, - { - "if": "value=split_road", - "then": "split_road - Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible" - }, - { - "if": "value=sport_pitch", - "then": "sport_pitch - A sport pitch" - }, - { - "if": "value=sports_centre", - "then": "sports_centre - Indoor and outdoor sports centres can be found on this layer" - }, - { - "if": "value=stairs", - "then": "stairs - Layer showing stairs and escalators" - }, - { - "if": "value=street_lamps", - "then": "street_lamps - A layer showing street lights" - }, - { - "if": "value=surveillance_camera", - "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" - }, - { - "if": "value=tertiary_education", - "then": "tertiary_education - Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)" - }, - { - "if": "value=ticket_machine", - "then": "ticket_machine - Find ticket machines for public transport tickets" - }, - { - "if": "value=ticket_validator", - "then": "ticket_validator - Find ticket validators to validate public transport tickets" - }, - { - "if": "value=toilet", - "then": "toilet - A layer showing (public) toilets" - }, - { - "if": "value=toilet_at_amenity", - "then": "toilet_at_amenity - A layer showing (public) toilets located at different places." - }, - { - "if": "value=trail", - "then": "trail - Aangeduide wandeltochten" - }, - { - "if": "value=transit_routes", - "then": "transit_routes - Layer showing bus lines" - }, - { - "if": "value=transit_stops", - "then": "transit_stops - Layer showing different types of transit stops." - }, - { - "if": "value=tree_node", - "then": "tree_node - A layer showing trees" - }, - { - "if": "value=usersettings", - "then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings" - }, - { - "if": "value=vending_machine", - "then": "vending_machine - Layer showing vending machines" - }, - { - "if": "value=veterinary", - "then": "veterinary - A layer showing veterinarians" - }, - { - "if": "value=viewpoint", - "then": "viewpoint - A nice viewpoint or nice view. Ideal to add an image if no other category fits" - }, - { - "if": "value=village_green", - "then": "village_green - A layer showing village-green (which are communal green areas, but not quite parks)" - }, - { - "if": "value=visitor_information_centre", - "then": "visitor_information_centre - A visitor center offers information about a specific attraction or place of interest where it is located." - }, - { - "if": "value=walls_and_buildings", - "then": "walls_and_buildings - Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user." - }, - { - "if": "value=waste_basket", - "then": "waste_basket - This is a public waste basket, thrash can, where you can throw away your thrash." - }, - { - "if": "value=waste_disposal", - "then": "waste_disposal - Waste Disposal Bin, medium to large bin for disposal of (household) waste" - }, - { - "if": "value=windturbine", - "then": "windturbine - Modern windmills generating electricity" - } - ] + "suggestions": [] }, "type": "array", "description": "If specified, these layers will be shown in the precise location picker and the new point will be snapped towards it.\nFor example, this can be used to snap against `walls_and_buildings` (e.g. to attach a defibrillator, an entrance, an artwork, ... to the wall)\nor to snap an obstacle (such as a bollard) to the `cycleways_and_roads`." @@ -12514,7 +11424,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -12522,7 +11432,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -13267,7 +12185,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -13374,6 +12292,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -14439,7 +13365,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -14548,6 +13474,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -15645,7 +14579,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -15754,6 +14688,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -16862,7 +15804,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -16973,6 +15915,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -28807,556 +27757,7 @@ "required": false, "hints": { "question": "Should the created point be snapped to a line layer?", - "suggestions": [ - { - "if": "value=address", - "then": "address - Addresses" - }, - { - "if": "value=advertising", - "then": "advertising - We will complete data from advertising features with reference, operator and lit" - }, - { - "if": "value=ambulancestation", - "then": "ambulancestation - An ambulance station is an area for storage of ambulance vehicles, medical equipment, personal protective equipment, and other medical supplies." - }, - { - "if": "value=animal_shelter", - "then": "animal_shelter - An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. " - }, - { - "if": "value=artwork", - "then": "artwork - An open map of statues, busts, graffitis and other artwork all over the world" - }, - { - "if": "value=atm", - "then": "atm - ATMs to withdraw money" - }, - { - "if": "value=bank", - "then": "bank - A financial institution to deposit money" - }, - { - "if": "value=barrier", - "then": "barrier - Obstacles while cycling, such as bollards and cycle barriers" - }, - { - "if": "value=bench", - "then": "bench - A bench is a wooden, metal, stone, … surface where a human can sit. This layers visualises them and asks a few questions about them." - }, - { - "if": "value=bench_at_pt", - "then": "bench_at_pt - A layer showing all public-transport-stops which do have a bench" - }, - { - "if": "value=bicycle_library", - "then": "bicycle_library - A facility where bicycles can be lent for longer period of times" - }, - { - "if": "value=bicycle_rental", - "then": "bicycle_rental - Bicycle rental stations" - }, - { - "if": "value=bicycle_tube_vending_machine", - "then": "bicycle_tube_vending_machine - A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)" - }, - { - "if": "value=bike_cafe", - "then": "bike_cafe - A bike café is a café geared towards cyclists, for example with services such as a pump, with lots of bicycle-related decoration, …" - }, - { - "if": "value=bike_cleaning", - "then": "bike_cleaning - A layer showing facilities where one can clean their bike" - }, - { - "if": "value=bike_parking", - "then": "bike_parking - A layer showing where you can park your bike" - }, - { - "if": "value=bike_repair_station", - "then": "bike_repair_station - A layer showing bicycle pumps and bicycle repair tool stands" - }, - { - "if": "value=bike_shop", - "then": "bike_shop - A shop specifically selling bicycles or related items" - }, - { - "if": "value=bike_themed_object", - "then": "bike_themed_object - A layer with bike-themed objects but who don't match any other layer" - }, - { - "if": "value=binocular", - "then": "binocular - Binoculars" - }, - { - "if": "value=birdhide", - "then": "birdhide - A birdhide" - }, - { - "if": "value=cafe_pub", - "then": "cafe_pub - A layer showing cafés and pubs where one can gather around a drink. The layer asks for some relevant questions" - }, - { - "if": "value=car_rental", - "then": "car_rental - Places where you can rent a car" - }, - { - "if": "value=charging_station", - "then": "charging_station - A charging station" - }, - { - "if": "value=climbing", - "then": "climbing - A dummy layer which contains tagrenderings, shared among the climbing layers" - }, - { - "if": "value=climbing_area", - "then": "climbing_area - An area where climbing is possible, e.g. a crag, site, boulder, … Contains aggregation of routes" - }, - { - "if": "value=climbing_club", - "then": "climbing_club - A climbing club or organisation" - }, - { - "if": "value=climbing_gym", - "then": "climbing_gym - A climbing gym" - }, - { - "if": "value=climbing_opportunity", - "then": "climbing_opportunity - Fallback layer with items on which climbing _might_ be possible. It is loaded when zoomed in a lot, to prevent duplicate items to be added" - }, - { - "if": "value=climbing_route", - "then": "climbing_route - A single climbing route and its properties. Some properties are derived from the containing features" - }, - { - "if": "value=clock", - "then": "clock - Layer with public clocks" - }, - { - "if": "value=conflation", - "then": "conflation - If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme." - }, - { - "if": "value=crab_address", - "then": "crab_address - Address data for Flanders by the governement, suited for import into OpenStreetMap. Datadump from 2021-10-26. This layer contains only visualisation logic. Import buttons should be added via an override. Note that HNRLABEL contains the original value, whereas _HNRLABEL contains a slightly cleaned version" - }, - { - "if": "value=crossings", - "then": "crossings - Crossings for pedestrians and cyclists" - }, - { - "if": "value=current_view", - "then": "current_view - A meta-layer which contains one single feature, namely the bounding box of the current map view. This can be used to trigger special actions. If a popup is defined for this layer, this popup will be accessible via an extra button on screen.\n\nThe icon on the button is the default icon of the layer, but can be customized by detecting 'button=yes'." - }, - { - "if": "value=cycleways_and_roads", - "then": "cycleways_and_roads - All infrastructure that someone can cycle over, accompanied with questions about this infrastructure" - }, - { - "if": "value=defibrillator", - "then": "defibrillator - A layer showing defibrillators which can be used in case of emergency. This contains public defibrillators, but also defibrillators which might need staff to fetch the actual device" - }, - { - "if": "value=dentist", - "then": "dentist - This layer shows dentist offices" - }, - { - "if": "value=direction", - "then": "direction - This layer visualizes directions" - }, - { - "if": "value=doctors", - "then": "doctors - This layer shows doctor offices" - }, - { - "if": "value=dogpark", - "then": "dogpark - A layer showing dogparks, which are areas where dog are allowed to run without a leash" - }, - { - "if": "value=drinking_water", - "then": "drinking_water - A layer showing drinking water fountains" - }, - { - "if": "value=elevator", - "then": "elevator - This layer show elevators and asks for operational status and elevator dimensions. Useful for wheelchair accessibility information" - }, - { - "if": "value=elongated_coin", - "then": "elongated_coin - Layer showing penny presses." - }, - { - "if": "value=entrance", - "then": "entrance - A layer showing entrances and offering capabilities to survey some advanced data which is important for e.g. wheelchair users (but also bicycle users, people who want to deliver, …)" - }, - { - "if": "value=etymology", - "then": "etymology - All objects which have an etymology known" - }, - { - "if": "value=extinguisher", - "then": "extinguisher - Map layer to show fire extinguishers." - }, - { - "if": "value=filters", - "then": "filters - This layer acts as library for common filters" - }, - { - "if": "value=fire_station", - "then": "fire_station - Map layer to show fire stations." - }, - { - "if": "value=fitness_centre", - "then": "fitness_centre - Layer showing fitness centres" - }, - { - "if": "value=fitness_station", - "then": "fitness_station - Find a fitness station near you, and add missing ones." - }, - { - "if": "value=fixme", - "then": "fixme - OSM objects that likely need to be fixed, based on a FIXME tag." - }, - { - "if": "value=food", - "then": "food - A layer showing restaurants and fast-food amenities (with a special rendering for friteries)" - }, - { - "if": "value=ghost_bike", - "then": "ghost_bike - A layer showing memorials for cyclists, killed in road accidents" - }, - { - "if": "value=governments", - "then": "governments - This layer show governmental buildings. It was setup as commissioned layer for the client of OSOC '22" - }, - { - "if": "value=gps_location", - "then": "gps_location - Meta layer showing the current location of the user. Add this to your theme and override the icon to change the appearance of the current location. The object will always have `id=gps` and will have _all_ the properties included in the [`Coordinates`-object](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates) (except latitude and longitude) returned by the browser, such as `speed`, `altitude`, `heading`, ...." - }, - { - "if": "value=gps_location_history", - "then": "gps_location_history - Meta layer which contains the previous locations of the user as single points. This is mainly for technical reasons, e.g. to keep match the distance to the modified object" - }, - { - "if": "value=gps_track", - "then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track." - }, - { - "if": "value=guidepost", - "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations" - }, - { - "if": "value=hackerspace", - "then": "hackerspace - Hackerspace" - }, - { - "if": "value=home_location", - "then": "home_location - Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap." - }, - { - "if": "value=hospital", - "then": "hospital - A layer showing hospital grounds" - }, - { - "if": "value=hotel", - "then": "hotel - Layer showing all hotels" - }, - { - "if": "value=hydrant", - "then": "hydrant - Map layer to show fire hydrants." - }, - { - "if": "value=ice_cream", - "then": "ice_cream - A place where ice cream is sold over the counter" - }, - { - "if": "value=icons", - "then": "icons - A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI" - }, - { - "if": "value=id_presets", - "then": "id_presets - Layer containing various presets and questions generated by ID. These are meant to be reused in other layers by importing the tagRenderings with `id_preset." - }, - { - "if": "value=import_candidate", - "then": "import_candidate - Layer used as template in the importHelper" - }, - { - "if": "value=indoors", - "then": "indoors - Basic indoor mapping: shows room outlines" - }, - { - "if": "value=information_board", - "then": "information_board - A layer showing touristical, road side information boards (e.g. giving information about the landscape, a building, a feature, a map, …)" - }, - { - "if": "value=kerbs", - "then": "kerbs - A layer showing kerbs." - }, - { - "if": "value=kindergarten_childcare", - "then": "kindergarten_childcare - Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other" - }, - { - "if": "value=last_click", - "then": "last_click - This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up" - }, - { - "if": "value=map", - "then": "map - A map, meant for tourists which is permanently installed in the public space" - }, - { - "if": "value=maproulette", - "then": "maproulette - Layer showing all tasks in MapRoulette" - }, - { - "if": "value=maproulette_challenge", - "then": "maproulette_challenge - Layer showing tasks of a single MapRoulette challenge. This layer is intended to be reused and extended in themes; refer to [the documentation](https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Integrating_Maproulette.md) on how to do this." - }, - { - "if": "value=maxspeed", - "then": "maxspeed - Shows the allowed speed for every road" - }, - { - "if": "value=memorial", - "then": "memorial - Layer showing memorial plaques, based upon a unofficial theme. Can be expanded to have multiple types of memorials later on" - }, - { - "if": "value=named_streets", - "then": "named_streets - Hidden layer with all streets which have a name. Useful to detect addresses" - }, - { - "if": "value=nature_reserve", - "then": "nature_reserve - A nature reserve is an area where nature can take its course" - }, - { - "if": "value=note", - "then": "note - This layer shows notes on OpenStreetMap. Having this layer in your theme will trigger the 'add new note' functionality in the 'addNewPoint'-popup (or if your theme has no presets, it'll enable adding notes)" - }, - { - "if": "value=observation_tower", - "then": "observation_tower - Towers with a panoramic view" - }, - { - "if": "value=osm_community_index", - "then": "osm_community_index - A layer showing the OpenStreetMap Communities" - }, - { - "if": "value=parcel_lockers", - "then": "parcel_lockers - Layer showing parcel lockers for collecting and sending parcels." - }, - { - "if": "value=parking", - "then": "parking - A layer showing car parkings" - }, - { - "if": "value=parking_spaces", - "then": "parking_spaces - Layer showing individual parking spaces." - }, - { - "if": "value=parking_ticket_machine", - "then": "parking_ticket_machine - Layer with parking ticket machines to pay for parking." - }, - { - "if": "value=pedestrian_path", - "then": "pedestrian_path - Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer" - }, - { - "if": "value=pharmacy", - "then": "pharmacy - A layer showing pharmacies, which (probably) dispense prescription drugs" - }, - { - "if": "value=physiotherapist", - "then": "physiotherapist - This layer shows physiotherapists" - }, - { - "if": "value=picnic_table", - "then": "picnic_table - The layer showing picnic tables" - }, - { - "if": "value=play_forest", - "then": "play_forest - Een speelbos is een vrij toegankelijke zone in een bos" - }, - { - "if": "value=playground", - "then": "playground - Playgrounds" - }, - { - "if": "value=postboxes", - "then": "postboxes - The layer showing postboxes." - }, - { - "if": "value=postoffices", - "then": "postoffices - A layer showing post offices." - }, - { - "if": "value=public_bookcase", - "then": "public_bookcase - A streetside cabinet with books, accessible to anyone" - }, - { - "if": "value=questions", - "then": "questions - Special library layer which does not need a '.questions'-prefix before being imported" - }, - { - "if": "value=railway_platforms", - "then": "railway_platforms - Find every platform in the station, and the train routes that use them." - }, - { - "if": "value=rainbow_crossings", - "then": "rainbow_crossings - A layer showing pedestrian crossings with rainbow paintings" - }, - { - "if": "value=range", - "then": "range - Meta-layer, simply showing a bbox in red" - }, - { - "if": "value=reception_desk", - "then": "reception_desk - A layer showing where the reception desks are and which asks some accessibility information" - }, - { - "if": "value=recycling", - "then": "recycling - A layer with recycling containers and centres" - }, - { - "if": "value=route_marker", - "then": "route_marker - Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." - }, - { - "if": "value=school", - "then": "school - Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles" - }, - { - "if": "value=selected_element", - "then": "selected_element - Highlights the currently selected element. Override this layer to have different colors" - }, - { - "if": "value=shelter", - "then": "shelter - Layer showing shelter structures" - }, - { - "if": "value=shops", - "then": "shops - A shop" - }, - { - "if": "value=shower", - "then": "shower - A layer showing (public) showers" - }, - { - "if": "value=slow_roads", - "then": "slow_roads - All carfree roads" - }, - { - "if": "value=speed_camera", - "then": "speed_camera - Layer showing speed cameras" - }, - { - "if": "value=speed_display", - "then": "speed_display - Layer showing speed displays that alert drivers of their speed." - }, - { - "if": "value=split_point", - "then": "split_point - Layer rendering the little scissors for the minimap in the 'splitRoadWizard'" - }, - { - "if": "value=split_road", - "then": "split_road - Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible" - }, - { - "if": "value=sport_pitch", - "then": "sport_pitch - A sport pitch" - }, - { - "if": "value=sports_centre", - "then": "sports_centre - Indoor and outdoor sports centres can be found on this layer" - }, - { - "if": "value=stairs", - "then": "stairs - Layer showing stairs and escalators" - }, - { - "if": "value=street_lamps", - "then": "street_lamps - A layer showing street lights" - }, - { - "if": "value=surveillance_camera", - "then": "surveillance_camera - This layer shows surveillance cameras and allows a contributor to update information and add new cameras" - }, - { - "if": "value=tertiary_education", - "then": "tertiary_education - Layer with all tertiary education institutes (ISCED:2011 levels 6,7 and 8)" - }, - { - "if": "value=ticket_machine", - "then": "ticket_machine - Find ticket machines for public transport tickets" - }, - { - "if": "value=ticket_validator", - "then": "ticket_validator - Find ticket validators to validate public transport tickets" - }, - { - "if": "value=toilet", - "then": "toilet - A layer showing (public) toilets" - }, - { - "if": "value=toilet_at_amenity", - "then": "toilet_at_amenity - A layer showing (public) toilets located at different places." - }, - { - "if": "value=trail", - "then": "trail - Aangeduide wandeltochten" - }, - { - "if": "value=transit_routes", - "then": "transit_routes - Layer showing bus lines" - }, - { - "if": "value=transit_stops", - "then": "transit_stops - Layer showing different types of transit stops." - }, - { - "if": "value=tree_node", - "then": "tree_node - A layer showing trees" - }, - { - "if": "value=usersettings", - "then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings" - }, - { - "if": "value=vending_machine", - "then": "vending_machine - Layer showing vending machines" - }, - { - "if": "value=veterinary", - "then": "veterinary - A layer showing veterinarians" - }, - { - "if": "value=viewpoint", - "then": "viewpoint - A nice viewpoint or nice view. Ideal to add an image if no other category fits" - }, - { - "if": "value=village_green", - "then": "village_green - A layer showing village-green (which are communal green areas, but not quite parks)" - }, - { - "if": "value=visitor_information_centre", - "then": "visitor_information_centre - A visitor center offers information about a specific attraction or place of interest where it is located." - }, - { - "if": "value=walls_and_buildings", - "then": "walls_and_buildings - Special builtin layer providing all walls and buildings. This layer is useful in presets for objects which can be placed against walls (e.g. AEDs, postboxes, entrances, addresses, surveillance cameras, …). This layer is invisible by default and not toggleable by the user." - }, - { - "if": "value=waste_basket", - "then": "waste_basket - This is a public waste basket, thrash can, where you can throw away your thrash." - }, - { - "if": "value=waste_disposal", - "then": "waste_disposal - Waste Disposal Bin, medium to large bin for disposal of (household) waste" - }, - { - "if": "value=windturbine", - "then": "windturbine - Modern windmills generating electricity" - } - ] + "suggestions": [] }, "type": "array", "description": "If specified, these layers will be shown in the precise location picker and the new point will be snapped towards it.\nFor example, this can be used to snap against `walls_and_buildings` (e.g. to attach a defibrillator, an entrance, an artwork, ... to the wall)\nor to snap an obstacle (such as a bollard) to the `cycleways_and_roads`." @@ -29413,7 +27814,7 @@ "type": "object", "properties": { "key": { - "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option", + "description": "question: What is the name of the attribute that should be written to?\nThis is the OpenStreetMap-key that that value will be written to\n\nifunset: do not offer a freeform textfield as answer option", "type": "string" }, "type": { @@ -29421,7 +27822,15 @@ "type": "string" }, "placeholder": { - "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" + "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", + "anyOf": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ] }, "helperArgs": { "description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'", @@ -30187,7 +28596,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -30296,6 +28705,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -31404,7 +29821,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -31515,6 +29932,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -32656,7 +31081,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -32767,6 +31192,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { @@ -33918,7 +32351,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -34031,6 +32464,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { diff --git a/src/assets/schemas/questionabletagrenderingconfigmeta.json b/src/assets/schemas/questionabletagrenderingconfigmeta.json index 1e40cb06d..99b977162 100644 --- a/src/assets/schemas/questionabletagrenderingconfigmeta.json +++ b/src/assets/schemas/questionabletagrenderingconfigmeta.json @@ -477,7 +477,7 @@ "ifunset": "do not offer a freeform textfield as answer option" }, "type": "string", - "description": "" + "description": "This is the OpenStreetMap-key that that value will be written to" }, { "path": [ @@ -580,6 +580,14 @@ "typehint": "translation", "question": "What placeholder text should be shown in the input-element if there is no input?" }, + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "description": "A (translated) text that is shown (as gray text) within the textfield" }, { diff --git a/test/Models/ThemeConfig/Conversion/CreateNoteImportLayer.spec.ts b/test/Models/ThemeConfig/Conversion/CreateNoteImportLayer.spec.ts index 9d2c922e3..ff4b3bdea 100644 --- a/test/Models/ThemeConfig/Conversion/CreateNoteImportLayer.spec.ts +++ b/test/Models/ThemeConfig/Conversion/CreateNoteImportLayer.spec.ts @@ -1,8 +1,5 @@ import { Utils } from "../../../../src/Utils" -import { - ConversionContext, - DesugaringContext, -} from "../../../../src/Models/ThemeConfig/Conversion/Conversion" +import { DesugaringContext } from "../../../../src/Models/ThemeConfig/Conversion/Conversion" import { LayerConfigJson } from "../../../../src/Models/ThemeConfig/Json/LayerConfigJson" import { TagRenderingConfigJson } from "../../../../src/Models/ThemeConfig/Json/TagRenderingConfigJson" import { PrepareLayer } from "../../../../src/Models/ThemeConfig/Conversion/PrepareLayer" @@ -10,6 +7,7 @@ import * as bookcases from "../../../../assets/layers/public_bookcase/public_boo import CreateNoteImportLayer from "../../../../src/Models/ThemeConfig/Conversion/CreateNoteImportLayer" import { describe, expect, it } from "vitest" import { QuestionableTagRenderingConfigJson } from "../../../../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" +import { ConversionContext } from "../../../../src/Models/ThemeConfig/Conversion/ConversionContext" describe("CreateNoteImportLayer", () => { it("should generate a layerconfig", () => { diff --git a/test/Models/ThemeConfig/Conversion/FixLegacyTheme.spec.ts b/test/Models/ThemeConfig/Conversion/FixLegacyTheme.spec.ts index 07a265a00..4d9416014 100644 --- a/test/Models/ThemeConfig/Conversion/FixLegacyTheme.spec.ts +++ b/test/Models/ThemeConfig/Conversion/FixLegacyTheme.spec.ts @@ -1,7 +1,8 @@ import LayoutConfig from "../../../../src/Models/ThemeConfig/LayoutConfig" import { FixLegacyTheme } from "../../../../src/Models/ThemeConfig/Conversion/LegacyJsonConvert" import { describe, expect, it } from "vitest" -import { ConversionContext } from "../../../../src/Models/ThemeConfig/Conversion/Conversion" + +import { ConversionContext } from "../../../../src/Models/ThemeConfig/Conversion/ConversionContext" describe("FixLegacyTheme", () => { it("should create a working theme config", () => { diff --git a/test/Models/ThemeConfig/Conversion/PrepareLayer.spec.ts b/test/Models/ThemeConfig/Conversion/PrepareLayer.spec.ts index 325e2e76f..ea22b575f 100644 --- a/test/Models/ThemeConfig/Conversion/PrepareLayer.spec.ts +++ b/test/Models/ThemeConfig/Conversion/PrepareLayer.spec.ts @@ -8,7 +8,8 @@ import { import { QuestionableTagRenderingConfigJson } from "../../../../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import RewritableConfigJson from "../../../../src/Models/ThemeConfig/Json/RewritableConfigJson" import { describe, expect, it } from "vitest" -import { ConversionContext } from "../../../../src/Models/ThemeConfig/Conversion/Conversion" + +import { ConversionContext } from "../../../../src/Models/ThemeConfig/Conversion/ConversionContext" describe("ExpandRewrite", () => { it("should not allow overlapping keys", () => { diff --git a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts index 11688e03c..f6ab8d60a 100644 --- a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts +++ b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts @@ -7,14 +7,12 @@ import LayerConfig from "../../../../src/Models/ThemeConfig/LayerConfig" import { ExtractImages } from "../../../../src/Models/ThemeConfig/Conversion/FixImages" import cyclofix from "../../../../src/assets/generated/themes/cyclofix.json" import { Tag } from "../../../../src/Logic/Tags/Tag" -import { - ConversionContext, - DesugaringContext, -} from "../../../../src/Models/ThemeConfig/Conversion/Conversion" +import { DesugaringContext } from "../../../../src/Models/ThemeConfig/Conversion/Conversion" import { And } from "../../../../src/Logic/Tags/And" import { describe, expect, it } from "vitest" import { QuestionableTagRenderingConfigJson } from "../../../../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import Constants from "../../../../src/Models/Constants" +import { ConversionContext } from "../../../../src/Models/ThemeConfig/Conversion/ConversionContext" const themeConfigJson: LayoutConfigJson = { description: "Descr",